{"openapi":"3.0.3","info":{"title":"Grant Harvester API","version":"1.0.0","description":"Federal grant opportunities for local communities. Closed or removed grants remain available by their source ID for six months."},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Issued gk_live_ API key."}},"schemas":{}},"paths":{"/v1/keys":{"get":{"summary":"List API key metadata","tags":["Keys"],"security":[{"bearerAuth":[]}],"x-required-scope":"admin","responses":{"200":{"description":"API key metadata was listed.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Inspect issued API keys","value":{"data":[{"id":"00000000-0000-4000-8000-000000000001","name":"Township integration","prefix":"EXAMPLE1","scopes":["search"],"rate_limit_rpm":60,"created_at":"2026-09-07T10:05:00.000Z","created_by":null,"last_used_at":null,"revoked_at":null}]}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"admin"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}}},"description":"List metadata for all API keys, including revoked keys. This is a privileged key-administration endpoint; API users may read its documentation, but calling it requires an API key with admin scope.","x-documentation":{"overview":"List metadata for all API keys, including revoked keys. This is a privileged key-administration endpoint; API users may read its documentation, but calling it requires an API key with admin scope.","steps":["Use a key with admin scope; a search-only key receives 403.","Find the intended key using its name, prefix, or id.","Inspect scopes and revoked_at before assigning the key to an integration or user."],"notes":["The result contains all keys ordered newest first, with no pagination parameters. Bearer secrets and stored hashes are never returned.","A browser account with administrator access is not automatically an admin-scoped API key. API Test also enforces the scopes of the assigned key.","The admin scope permits key management and does not implicitly permit grant search; a key that needs both must explicitly contain search and admin.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Inspect issued API keys","request":{"parameters":{}},"response":{"status":200,"body":{"data":[{"id":"00000000-0000-4000-8000-000000000001","name":"Township integration","prefix":"EXAMPLE1","scopes":["search"],"rate_limit_rpm":60,"created_at":"2026-09-07T10:05:00.000Z","created_by":null,"last_used_at":null,"revoked_at":null}]}}}],"responseFields":[{"name":"data","type":"object[]","description":"API key metadata, newest first, including revoked keys."},{"name":"data[].id","type":"string","description":"API key UUID. Use this value in the revoke path; it is not the bearer secret."},{"name":"data[].name","type":"string","description":"Human-readable name chosen when issuing the key."},{"name":"data[].prefix","type":"string","description":"Eight-character display identifier used to recognize a key. This is not sufficient to authenticate."},{"name":"data[].scopes","type":"string[]","description":"Explicit permissions: search allows grant and status endpoints; admin allows key management. Neither scope automatically includes the other."},{"name":"data[].rate_limit_rpm","type":"integer","description":"Shared per-key request budget per minute. Calls from API Test and external systems consume the same key’s budget."},{"name":"data[].created_at","type":"string","description":"UTC timestamp when the key was issued."},{"name":"data[].created_by","type":"string | null","description":"Creator user UUID, when known. This field does not itself assign the key to a user."},{"name":"data[].last_used_at","type":"string | null","description":"Most recent recorded use; updates are throttled to approximately once per minute. Null means no recorded use."},{"name":"data[].revoked_at","type":"string | null","description":"UTC revocation timestamp. Null means active; revoked keys cannot authenticate."}]}},"post":{"summary":"Issue an API key; the secret is shown once","tags":["Keys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","maxLength":100,"description":"A name describing who uses this key.","minLength":1},"scopes":{"type":"array","minItems":1,"maxItems":2,"uniqueItems":true,"items":{"type":"string","enum":["search","admin"]},"default":["search"],"description":"Explicit permissions: search allows grant, source, facet, and status endpoints; admin allows key management. Neither includes the other."},"rate_limit_rpm":{"type":"integer","minimum":1,"maximum":10000,"default":60,"description":"Per-key request budget per minute, shared by external callers and API Test."}}}}}},"security":[{"bearerAuth":[]}],"x-required-scope":"admin","responses":{"201":{"description":"The key was created. Save key securely; it is shown once.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Issue a township search key","value":{"id":"00000000-0000-4000-8000-000000000001","name":"Township integration","prefix":"EXAMPLE1","scopes":["search"],"rate_limit_rpm":60,"created_at":"2026-09-07T10:05:00.000Z","created_by":null,"last_used_at":null,"revoked_at":null,"key":"YOUR_NEW_API_KEY_RETURNED_ONCE"}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"400":{"description":"The name, scopes, request budget, or JSON body is invalid. Unknown body properties are rejected.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The name, scopes, request budget, or JSON body is invalid. Unknown body properties are rejected."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"admin"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}}},"description":"Issue a new API key with explicit permissions and a request budget. The full secret is returned once in the successful response and cannot be retrieved later.","x-documentation":{"overview":"Issue a new API key with explicit permissions and a request budget. The full secret is returned once in the successful response and cannot be retrieved later.","steps":["Use an existing API key with admin scope and send Content-Type: application/json.","Provide a descriptive name. Choose scopes and rate_limit_rpm, or accept their schema defaults.","On HTTP 201, save the returned key securely before closing the response.","If a signed-in user should use this key in API Test, an administrator must separately assign it to that user in Users."],"notes":["Default scopes are search and the default request budget is 60 per minute. Set scopes to [\"search\", \"admin\"] only when the consumer needs both grant access and key management.","Issuing a key does not create a user or assign the key to a user. API-only users cannot be assigned an admin-scoped key.","The request is not idempotent: sending the POST again creates another key, even if the name is the same. If the response is lost, inspect existing keys and revoke an unwanted key before issuing a replacement.","The secret is redacted in API Logs. Copy it from the immediate response; neither the keys list nor logs can recover it.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Issue a township search key","request":{"parameters":{},"body":{"name":"Township integration","scopes":["search"],"rate_limit_rpm":60}},"response":{"status":201,"body":{"id":"00000000-0000-4000-8000-000000000001","name":"Township integration","prefix":"EXAMPLE1","scopes":["search"],"rate_limit_rpm":60,"created_at":"2026-09-07T10:05:00.000Z","created_by":null,"last_used_at":null,"revoked_at":null,"key":"YOUR_NEW_API_KEY_RETURNED_ONCE"}}}],"responseFields":[{"name":"id","type":"string","description":"API key UUID. Use this value in the revoke path; it is not the bearer secret."},{"name":"name","type":"string","description":"Human-readable name chosen when issuing the key."},{"name":"prefix","type":"string","description":"Eight-character display identifier used to recognize a key. This is not sufficient to authenticate."},{"name":"scopes","type":"string[]","description":"Explicit permissions: search allows grant and status endpoints; admin allows key management. Neither scope automatically includes the other."},{"name":"rate_limit_rpm","type":"integer","description":"Shared per-key request budget per minute. Calls from API Test and external systems consume the same key’s budget."},{"name":"created_at","type":"string","description":"UTC timestamp when the key was issued."},{"name":"created_by","type":"string | null","description":"Creator user UUID, when known. This field does not itself assign the key to a user."},{"name":"last_used_at","type":"string | null","description":"Most recent recorded use; updates are throttled to approximately once per minute. Null means no recorded use."},{"name":"revoked_at","type":"string | null","description":"UTC revocation timestamp. Null means active; revoked keys cannot authenticate."},{"name":"key","type":"string","description":"Full bearer secret, returned only on creation. Real keys use gk_live_ followed by 32 letters or digits. Treat it as a password and never place it in a URL or public browser bundle."}]}}},"/v1/keys/{id}":{"delete":{"summary":"Revoke an API key immediately","tags":["Keys"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"UUID of the API key to revoke, from GET /v1/keys. This is not the bearer secret or display prefix."}],"security":[{"bearerAuth":[]}],"x-required-scope":"admin","responses":{"200":{"description":"The key is revoked; its metadata is returned.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Revoke a selected key","value":{"id":"00000000-0000-4000-8000-000000000001","name":"Township integration","prefix":"EXAMPLE1","scopes":["search"],"rate_limit_rpm":60,"created_at":"2026-09-07T10:05:00.000Z","created_by":null,"last_used_at":null,"revoked_at":"2026-09-07T12:05:00.000Z"}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"400":{"description":"The id path parameter must be a UUID.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The id path parameter must be a UUID."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"admin"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"404":{"description":"No API key exists with this id.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"No API key exists with this id."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}}},"description":"Revoke an API key immediately using its UUID. The metadata record remains available for auditing, but the key can no longer authenticate or run assigned API tests.","x-documentation":{"overview":"Revoke an API key immediately using its UUID. The metadata record remains available for auditing, but the key can no longer authenticate or run assigned API tests.","steps":["Use a different active key with admin scope when revoking an integration’s key.","Get the target id from GET /v1/keys and confirm its name and prefix.","Send DELETE /v1/keys/{id} with the UUID substituted in the path.","Confirm revoked_at is set, then assign a replacement to any affected users or integrations."],"notes":["Revocation is permanent; there is no endpoint to reactivate the same secret. Create and assign a new key when needed.","Repeating DELETE for an existing revoked key returns its metadata and original revocation timestamp. An unknown UUID returns 404.","You can revoke the key authenticating this request. The request can complete, but later calls with that key will fail. Take care to retain another administrative access route.","Users assigned the revoked key cannot use it for API Test. Their retained historical key logs remain readable within the UTC retention window.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Revoke a selected key","request":{"parameters":{"id":"00000000-0000-4000-8000-000000000001"}},"response":{"status":200,"body":{"id":"00000000-0000-4000-8000-000000000001","name":"Township integration","prefix":"EXAMPLE1","scopes":["search"],"rate_limit_rpm":60,"created_at":"2026-09-07T10:05:00.000Z","created_by":null,"last_used_at":null,"revoked_at":"2026-09-07T12:05:00.000Z"}}}],"responseFields":[{"name":"id","type":"string","description":"API key UUID. Use this value in the revoke path; it is not the bearer secret."},{"name":"name","type":"string","description":"Human-readable name chosen when issuing the key."},{"name":"prefix","type":"string","description":"Eight-character display identifier used to recognize a key. This is not sufficient to authenticate."},{"name":"scopes","type":"string[]","description":"Explicit permissions: search allows grant and status endpoints; admin allows key management. Neither scope automatically includes the other."},{"name":"rate_limit_rpm","type":"integer","description":"Shared per-key request budget per minute. Calls from API Test and external systems consume the same key’s budget."},{"name":"created_at","type":"string","description":"UTC timestamp when the key was issued."},{"name":"created_by","type":"string | null","description":"Creator user UUID, when known. This field does not itself assign the key to a user."},{"name":"last_used_at","type":"string | null","description":"Most recent recorded use; updates are throttled to approximately once per minute. Null means no recorded use."},{"name":"revoked_at","type":"string | null","description":"UTC revocation timestamp. Null means active; revoked keys cannot authenticate."}]}}},"/health":{"get":{"summary":"Process health; no database access","tags":["Status"],"responses":{"200":{"description":"The API process is responding.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Check process health","value":{"status":"ok","service":"api"}}}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}}}},"description":"Check whether the API process can answer HTTP requests. This is a lightweight liveness check and is available without an API key.","x-documentation":{"overview":"Check whether the API process can answer HTTP requests. This is a lightweight liveness check and is available without an API key.","steps":["Send GET /health to the same host as the API.","Expect HTTP 200 with status set to ok and service set to api.","Use GET /v1/status for data freshness or GET /v1/health to check databases and the embedding service."],"notes":["This endpoint does not query databases or validate the grant index. An ok response does not prove search or dependencies are functioning.","This endpoint is outside /v1 and is not included in API Logs. Frequent external health polling can prevent an idle API service from sleeping."],"examples":[{"title":"Check process health","request":{"parameters":{}},"response":{"status":200,"body":{"status":"ok","service":"api"}}}],"responseFields":[{"name":"status","type":"string","description":"ok when the API process answers normally."},{"name":"service","type":"string","description":"The responding service, api."}]}}},"/v1/status":{"get":{"summary":"Corpus freshness and source health","tags":["Status"],"security":[{"bearerAuth":[]}],"x-required-scope":"search","responses":{"200":{"description":"Freshness information was read successfully. Inspect state even when the HTTP status is 200.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Inspect catalog freshness","value":{"as_of":"2026-09-07T12:05:00.000Z","state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"sources":[{"source":"grants.gov","enabled":true,"state":"fresh","last_success_at":"2026-09-07T10:05:00.000Z","last_attempt_at":"2026-09-07T10:05:00.000Z","expected_interval_hours":24,"stale_after_hours":30,"max_no_change_runs":3,"age_hours":2,"next_expected_at":"2026-09-08T10:05:00.000Z","consecutive_failures":0,"consecutive_no_change_runs":0,"pending_embeddings":0,"last_error":null,"last_run":{"status":"succeeded","run_status":"succeeded","rows_inserted":4,"rows_updated":2}}]}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"search"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"503":{"description":"The grant database is unavailable or is not configured. Retry-After is 30 seconds.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The grant database is unavailable or is not configured. Retry-After is 30 seconds."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}}},"description":"Read the freshness of the harvested grant catalog and the latest known outcome for each source. Use this endpoint for an external monitor that checks data age and harvest failures.","x-documentation":{"overview":"Read the freshness of the harvested grant catalog and the latest known outcome for each source. Use this endpoint for an external monitor that checks data age and harvest failures.","steps":["Use an API key with search scope.","Read the overall state and data_as_of fields.","Inspect sources for the affected source, consecutive failures, pending embeddings, and latest run information.","Treat never_run, failing, stale, or unavailable as conditions for your external monitoring system to handle. No outbound alerts are sent by this service."],"notes":["This reads stored harvest information; it does not contact Grants.gov or start a new harvest.","Freshness snapshots are cached for up to 60 seconds; age and threshold state are recalculated as time passes.","fresh means within the expected interval; late exceeds that interval; stale exceeds stale_after_hours; failing means recent harvest failures; not_changing means the configured number of unchanged runs has been reached. never_run means no successful harvest, and disabled means the source is disabled.","X-Data-State reports catalog freshness on /v1 responses. X-Data-As-Of and X-Data-Age-Hours are included when known. A stale catalog can still return HTTP 200, so inspect freshness before making time-sensitive decisions.","A sleeping service can make the first request slower. Semantic search and the full dependency check may also wake the embedding service. Use a generous client timeout and respect Retry-After on 503 responses.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Inspect catalog freshness","request":{"parameters":{}},"response":{"status":200,"body":{"as_of":"2026-09-07T12:05:00.000Z","state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"sources":[{"source":"grants.gov","enabled":true,"state":"fresh","last_success_at":"2026-09-07T10:05:00.000Z","last_attempt_at":"2026-09-07T10:05:00.000Z","expected_interval_hours":24,"stale_after_hours":30,"max_no_change_runs":3,"age_hours":2,"next_expected_at":"2026-09-08T10:05:00.000Z","consecutive_failures":0,"consecutive_no_change_runs":0,"pending_embeddings":0,"last_error":null,"last_run":{"status":"succeeded","run_status":"succeeded","rows_inserted":4,"rows_updated":2}}]}}}],"responseFields":[{"name":"as_of","type":"string","description":"UTC time when this status response was produced."},{"name":"state","type":"string","description":"Overall state of enabled sources: fresh, late, not_changing, failing, stale, never_run, unavailable, or disabled. A successful HTTP response does not by itself mean the catalog is fresh."},{"name":"data_as_of","type":"string | null","description":"UTC timestamp of the oldest successful harvest among enabled sources. Null if an enabled source has never completed successfully. This is a harvest timestamp, not the publisher’s modification time."},{"name":"stalest_source","type":"string | null","description":"Source with the oldest or missing successful harvest; null when no source is enabled."},{"name":"age_hours","type":"number | null","description":"Hours since that successful harvest, rounded to one decimal; null if unknown."},{"name":"sources","type":"object[]","description":"Per-source freshness and stored harvest diagnostics."},{"name":"sources[].source","type":"string","description":"Source identifier, currently grants.gov."},{"name":"sources[].enabled","type":"boolean","description":"Whether harvesting is enabled for this source."},{"name":"sources[].state","type":"string","description":"Freshness state for this source using the same values as the overall state."},{"name":"sources[].last_success_at","type":"string | null","description":"UTC completion time of the most recent successful source harvest."},{"name":"sources[].last_attempt_at","type":"string | null","description":"UTC completion time of the most recent non-skipped attempt."},{"name":"sources[].age_hours","type":"number | null","description":"Hours since the last successful harvest, rounded to one decimal."},{"name":"sources[].expected_interval_hours","type":"integer","description":"Expected interval between successful harvests, used for the late threshold."},{"name":"sources[].stale_after_hours","type":"integer","description":"Age threshold after which this source is stale."},{"name":"sources[].max_no_change_runs","type":"integer","description":"Unchanged-run threshold used for the not_changing state."},{"name":"sources[].next_expected_at","type":"string | null","description":"Last success plus expected interval. This is a freshness expectation, not confirmation of a scheduled job start."},{"name":"sources[].consecutive_failures","type":"integer","description":"Failed source attempts since the last success."},{"name":"sources[].consecutive_no_change_runs","type":"integer","description":"Consecutive successful runs that did not insert, update, or remove grants."},{"name":"sources[].pending_embeddings","type":"integer","description":"Unswept records with missing or outdated embeddings. Semantic search excludes records without a current compatible embedding."},{"name":"sources[].source_file","type":"string | null","description":"Source snapshot file associated with the last success."},{"name":"sources[].source_sha256","type":"string | null","description":"SHA-256 digest of that snapshot, when recorded."},{"name":"sources[].publisher_changed","type":"boolean | null","description":"Whether the publisher snapshot changed at the last successful harvest."},{"name":"sources[].last_success_run_id","type":"string | null","description":"UUID of the most recent successful harvest run."},{"name":"sources[].last_error","type":"string | null","description":"Stored message from the most recent attempted source run, when it failed."},{"name":"sources[].last_run","type":"object | null","description":"Most recent source-run diagnostic record, including status, run_status, started_at, finished_at, record counts, rows_inserted, rows_updated, rows_deleted, rows_embedded, http_requests, error, git_sha, and trigger. May be null before the first run."}]}}},"/v1/health":{"get":{"summary":"Check database and embedding dependencies","tags":["Status"],"security":[{"bearerAuth":[]}],"x-required-scope":"search","responses":{"200":{"description":"Every dependency check passed.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"All dependencies available","value":{"status":"ok","dependencies":{"settings_database":{"status":"ok"},"grant_database":{"status":"ok"},"embedder":{"status":"ok"}}}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"search"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"503":{"description":"At least one dependency is unavailable. The usual body has status and dependencies; failures before these checks can return the standard error body. Retry-After is 30 seconds.","headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example2":{"summary":"Embedding dependency unavailable","value":{"status":"degraded","dependencies":{"settings_database":{"status":"ok"},"grant_database":{"status":"ok"},"embedder":{"status":"unavailable"}}}}}}}}},"description":"Run a live dependency check against the settings database, grant database, and embedding service. Use this to distinguish an operational problem from an old but readable grant catalog.","x-documentation":{"overview":"Run a live dependency check against the settings database, grant database, and embedding service. Use this to distinguish an operational problem from an old but readable grant catalog.","steps":["Use an API key with search scope and allow the embedding service time to wake.","Check HTTP status and the top-level status field.","Inspect every dependencies entry. A 503 response still contains individual results when the dependency checks can be performed."],"notes":["HTTP 200 means all three dependencies passed; HTTP 503 with status degraded means at least one failed.","The embedder check verifies the expected model and 1024-dimensional output configuration. It does not perform a full search or harvest.","A dependency check can take up to roughly 90 seconds while the embedder wakes or times out. Repeated checks may keep services awake; this endpoint does not schedule alerts.","X-Data-State reports catalog freshness on /v1 responses. X-Data-As-Of and X-Data-Age-Hours are included when known. A stale catalog can still return HTTP 200, so inspect freshness before making time-sensitive decisions.","A sleeping service can make the first request slower. Semantic search and the full dependency check may also wake the embedding service. Use a generous client timeout and respect Retry-After on 503 responses.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"All dependencies available","request":{"parameters":{}},"response":{"status":200,"body":{"status":"ok","dependencies":{"settings_database":{"status":"ok"},"grant_database":{"status":"ok"},"embedder":{"status":"ok"}}}}},{"title":"Embedding dependency unavailable","request":{"parameters":{}},"response":{"status":503,"body":{"status":"degraded","dependencies":{"settings_database":{"status":"ok"},"grant_database":{"status":"ok"},"embedder":{"status":"unavailable"}}}}}],"responseFields":[{"name":"status","type":"string","description":"ok if all dependencies pass, otherwise degraded."},{"name":"dependencies.settings_database.status","type":"string","description":"ok or unavailable for this dependency."},{"name":"dependencies.grant_database.status","type":"string","description":"ok or unavailable for this dependency."},{"name":"dependencies.embedder.status","type":"string","description":"ok or unavailable for this dependency."}]}}},"/v1/grants":{"get":{"summary":"List active grant opportunities","tags":["Grants"],"parameters":[{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Opportunity status. Repeat this parameter to select several.","enum":["posted","forecasted"]}},"in":"query","name":"status","required":false,"description":"Opportunity status. Repeat this parameter to select several.","style":"form","explode":true},{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Source identifiers. Repeat this parameter to select several."}},"in":"query","name":"source","required":false,"description":"Source identifiers. Repeat this parameter to select several.","style":"form","explode":true},{"schema":{"type":"string","maxLength":300},"in":"query","name":"agency","required":false,"description":"Exact agency code, or agency name containing this text."},{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Funding instruments; matches any selected value."}},"in":"query","name":"type","required":false,"description":"Funding instruments; matches any selected value.","style":"form","explode":true},{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Funding categories; matches any selected value."}},"in":"query","name":"category","required":false,"description":"Funding categories; matches any selected value.","style":"form","explode":true},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"min_award","required":false,"description":"Minimum award amount. Matches overlapping award ranges; unknown bounds are unbounded."},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"max_award","required":false,"description":"Maximum award amount. Matches overlapping award ranges; unknown bounds are unbounded."},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"min_total","required":false,"description":"Minimum total program funding. Unknown totals are excluded when a total filter is supplied."},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"max_total","required":false,"description":"Maximum total program funding. Unknown totals are excluded when a total filter is supplied."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"posted_after","required":false,"description":"Posted on or after this date. Unknown posting dates are excluded."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"posted_before","required":false,"description":"Posted on or before this date. Unknown posting dates are excluded."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"closes_after","required":false,"description":"Closes on or after this date. Undated rolling grants remain included by default."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"closes_before","required":false,"description":"Closes on or before this date. Undated rolling grants remain included by default."},{"schema":{"type":"boolean","default":true},"in":"query","name":"include_rolling","required":false,"description":"Include rolling and undated opportunities."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"in":"query","name":"limit","required":false,"description":"Maximum records per page."},{"schema":{"type":"string","maxLength":4096},"in":"query","name":"cursor","required":false,"description":"Opaque continuation cursor from the previous response; valid only for the same filters."}],"security":[{"bearerAuth":[]}],"x-required-scope":"search","responses":{"200":{"description":"A page of active grants, including an empty page when no results match.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Browse grants for a township budget","value":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":1,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}},"example2":{"summary":"First page of a larger result set","value":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":2,"matching_total":2,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":"REPLACE_WITH_META_NEXT_CURSOR","page_size":1,"total_pages":2}}},"example3":{"summary":"Continue using the previous response cursor","value":{"data":[{"id":"124","source":"grants.gov","source_id":"EXAMPLE-124","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":2,"matching_total":2,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":1,"total_pages":2}}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"400":{"description":"Invalid filter, range, date, limit, unknown parameter, or cursor. Correct the request before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"Invalid filter, range, date, limit, unknown parameter, or cursor. Correct the request before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"search"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"503":{"description":"The grant database is unavailable or not configured. Retry-After is 30 seconds.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The grant database is unavailable or not configured. Retry-After is 30 seconds."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}}},"description":"Browse active grant opportunities using structured filters. This is useful for building a township grant list, synchronizing currently available results, or narrowing by agency, dates, and funding amounts.","x-documentation":{"overview":"Browse active grant opportunities using structured filters. This is useful for building a township grant list, synchronizing currently available results, or narrowing by agency, dates, and funding amounts.","steps":["Use an API key with search scope.","Optionally get the available agency, type, and category values from GET /v1/meta/facets.","Send the filters you need and choose a limit between 1 and 500.","Read each grant and freshness metadata, then follow meta.next_cursor until it is null."],"notes":["Results are ordered by ascending internal id. This endpoint does not accept q or mode; use GET /v1/grants/search for text queries.","Only active, unswept posted or forecasted records with no expired deadline are listed. Confirmed duplicate records are collapsed. This collection is not a historical export.","Different filter names are combined with AND. Repeated status, source, type, or category parameters match any selected value within that filter: status=posted&status=forecasted. Do not send a comma-separated list.","Dates use YYYY-MM-DD and inclusive bounds. include_rolling defaults to true, so undated records can remain in closing-date searches. Set include_rolling=false when you need only dated, non-rolling opportunities.","Award filters match overlapping award ranges; unknown award bounds are unbounded. Total-funding filters exclude unknown program totals. Amount filters are numeric US-dollar values, without currency symbols or thousands separators.","Unknown parameters, invalid dates, out-of-range limits, or a minimum greater than its maximum return 400. Discover exact agency, type, and category values using GET /v1/meta/facets.","To paginate, copy meta.next_cursor exactly into cursor and retain the same endpoint and filter values, including q and mode for search. Do not edit or decode the cursor. Changing limit is supported, but retaining it keeps page counts easier to compare.","Counts describe the current catalog and can change if a harvest completes between requests; pagination is not a fixed historical snapshot. Stop when meta.next_cursor is null.","X-Data-State reports catalog freshness on /v1 responses. X-Data-As-Of and X-Data-Age-Hours are included when known. A stale catalog can still return HTTP 200, so inspect freshness before making time-sensitive decisions.","A sleeping service can make the first request slower. Semantic search and the full dependency check may also wake the embedding service. Use a generous client timeout and respect Retry-After on 503 responses.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Browse grants for a township budget","request":{"parameters":{"source":["grants.gov"],"status":["posted","forecasted"],"min_award":25000,"max_award":250000,"limit":25}},"response":{"status":200,"body":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":1,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}}},{"title":"First page of a larger result set","request":{"parameters":{"source":["grants.gov"],"limit":1}},"response":{"status":200,"body":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":2,"matching_total":2,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":"REPLACE_WITH_META_NEXT_CURSOR","page_size":1,"total_pages":2}}}},{"title":"Continue using the previous response cursor","request":{"parameters":{"source":["grants.gov"],"limit":1,"cursor":"REPLACE_WITH_META_NEXT_CURSOR"}},"response":{"status":200,"body":{"data":[{"id":"124","source":"grants.gov","source_id":"EXAMPLE-124","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":2,"matching_total":2,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":1,"total_pages":2}}}}],"responseFields":[{"name":"data","type":"object[]","description":"This page of normalized grants; an empty array is a successful response when nothing matches."},{"name":"data[].id","type":"string","description":"Internal grant identifier, encoded as a decimal string so large database identifiers remain exact. Use source and source_id for the detail endpoint."},{"name":"data[].source","type":"string","description":"Publisher identifier. The initial source is grants.gov."},{"name":"data[].source_id","type":"string","description":"Stable identifier assigned by the source. Combine with source to uniquely identify a grant."},{"name":"data[].opportunity_number","type":"string | null","description":"Publisher’s opportunity or funding notice number. This can differ from source_id."},{"name":"data[].kind","type":"string","description":"Normalized record kind: synopsis or forecast."},{"name":"data[].status","type":"string","description":"Normalized status: posted, forecasted, closed, or archived. Collection endpoints return active posted and forecasted records."},{"name":"data[].status_derived","type":"boolean","description":"True if the status was inferred during normalization rather than supplied directly."},{"name":"data[].title","type":"string","description":"Published opportunity title."},{"name":"data[].url","type":"string | null","description":"Publisher’s opportunity page. Consult this page for current application instructions and eligibility."},{"name":"data[].agency_code","type":"string | null","description":"Publishing agency code; accepted by the agency filter."},{"name":"data[].agency_name","type":"string | null","description":"Publishing agency name; the agency filter also supports a case-insensitive substring of this name."},{"name":"data[].top_level_agency_name","type":"string | null","description":"Parent agency or department, when available."},{"name":"data[].grant_type","type":"string[]","description":"Normalized funding instrument labels, such as Grant or Cooperative Agreement. Use current facet values, including capitalization, in type filters."},{"name":"data[].funding_categories","type":"string[]","description":"Publisher’s normalized funding categories. Use current facet values in category filters."},{"name":"data[].assistance_listings","type":"string[]","description":"Assistance listing identifiers associated with this opportunity."},{"name":"data[].eligible_applicants","type":"string[]","description":"Published applicant categories. Read these and the original notice before treating an opportunity as suitable for a township."},{"name":"data[].post_date","type":"string | null","description":"Publication date in YYYY-MM-DD format; null if unknown."},{"name":"data[].close_date","type":"string | null","description":"Application deadline date in YYYY-MM-DD format. Null can mean a rolling or undated opportunity; it does not guarantee applications remain open."},{"name":"data[].archive_date","type":"string | null","description":"Publisher’s archive date in YYYY-MM-DD format, if supplied."},{"name":"data[].last_updated_date","type":"string | null","description":"Publisher’s last update date in YYYY-MM-DD format, if supplied."},{"name":"data[].is_rolling","type":"boolean","description":"Whether the normalized opportunity is marked as rolling. Undated opportunities are also handled by include_rolling."},{"name":"data[].award_floor","type":"string | null","description":"Lower award amount in US dollars. Decimal database values are returned as strings; null means unknown, not zero."},{"name":"data[].award_ceiling","type":"string | null","description":"Upper award amount in US dollars, returned as a decimal string; null means unknown."},{"name":"data[].estimated_total_funding","type":"string | null","description":"Estimated funding across the whole program in US dollars, returned as a decimal string; distinct from an individual award."},{"name":"data[].expected_awards","type":"integer | null","description":"Expected number of awards, if published."},{"name":"data[].cost_sharing_required","type":"boolean | null","description":"Whether matching funds or cost sharing are required. Null means the source does not specify."},{"name":"data[].description","type":"string | null","description":"Normalized description. Treat source text as untrusted content when displaying it in your application."},{"name":"data[].data","type":"string","description":"Combined normalized grant document in Markdown, used for keyword search. It includes more context than description alone. Semantic indexing uses the title, agency, categories, eligible applicants, and description."},{"name":"data[].embedding_model","type":"string | null","description":"Embedding model associated with the stored index, when indexed. The API does not return embedding vectors."},{"name":"data[].first_seen_run","type":"string","description":"UUID of the harvest that first inserted the grant."},{"name":"data[].last_seen_run","type":"string","description":"UUID of the most recent harvest that observed the grant."},{"name":"data[].first_seen_at","type":"string","description":"UTC timestamp when the grant was first stored."},{"name":"data[].updated_at","type":"string","description":"UTC timestamp recorded for the stored grant’s update."},{"name":"data[].swept_at","type":"string | null","description":"UTC soft-deletion timestamp. Null for collection results; retained detail records may have a value and are eligible for permanent deletion six calendar months after this time."},{"name":"filtered_total","type":"integer","description":"Count after active, duplicate, and structured filters, before keyword matching or semantic index eligibility. It can exceed matching_total."},{"name":"matching_total","type":"integer","description":"Actual number of matching records across all pages, before applying the continuation cursor. This count determines total_pages."},{"name":"meta.next_cursor","type":"string | null","description":"Opaque signed continuation token. Request another page with cursor equal to this value; null means no further page."},{"name":"meta.page_size","type":"integer","description":"Requested limit (100 by default). The last page can contain fewer records."},{"name":"meta.total_pages","type":"integer","description":"Ceiling of matching_total divided by page_size; zero when no records match. It describes the full result set, not the pages remaining after a cursor."},{"name":"meta.state","type":"string","description":"Overall state of enabled sources: fresh, late, not_changing, failing, stale, never_run, unavailable, or disabled. A successful HTTP response does not by itself mean the catalog is fresh."},{"name":"meta.data_as_of","type":"string | null","description":"UTC timestamp of the oldest successful harvest among enabled sources. Null if an enabled source has never completed successfully. This is a harvest timestamp, not the publisher’s modification time."},{"name":"meta.stalest_source","type":"string | null","description":"Source with the oldest or missing successful harvest; null when no source is enabled."},{"name":"meta.age_hours","type":"number | null","description":"Hours since that successful harvest, rounded to one decimal; null if unknown."}]}}},"/v1/grants/search":{"get":{"summary":"Filter and rank grant opportunities","tags":["Grants"],"parameters":[{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Opportunity status. Repeat this parameter to select several.","enum":["posted","forecasted"]}},"in":"query","name":"status","required":false,"description":"Opportunity status. Repeat this parameter to select several.","style":"form","explode":true},{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Source identifiers. Repeat this parameter to select several."}},"in":"query","name":"source","required":false,"description":"Source identifiers. Repeat this parameter to select several.","style":"form","explode":true},{"schema":{"type":"string","maxLength":300},"in":"query","name":"agency","required":false,"description":"Exact agency code, or agency name containing this text."},{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Funding instruments; matches any selected value."}},"in":"query","name":"type","required":false,"description":"Funding instruments; matches any selected value.","style":"form","explode":true},{"schema":{"type":"array","minItems":1,"maxItems":30,"items":{"type":"string","maxLength":300,"description":"Funding categories; matches any selected value."}},"in":"query","name":"category","required":false,"description":"Funding categories; matches any selected value.","style":"form","explode":true},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"min_award","required":false,"description":"Minimum award amount. Matches overlapping award ranges; unknown bounds are unbounded."},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"max_award","required":false,"description":"Maximum award amount. Matches overlapping award ranges; unknown bounds are unbounded."},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"min_total","required":false,"description":"Minimum total program funding. Unknown totals are excluded when a total filter is supplied."},{"schema":{"type":"number","minimum":0,"maximum":1000000000000000},"in":"query","name":"max_total","required":false,"description":"Maximum total program funding. Unknown totals are excluded when a total filter is supplied."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"posted_after","required":false,"description":"Posted on or after this date. Unknown posting dates are excluded."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"posted_before","required":false,"description":"Posted on or before this date. Unknown posting dates are excluded."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"closes_after","required":false,"description":"Closes on or after this date. Undated rolling grants remain included by default."},{"schema":{"type":"string","maxLength":300,"format":"date"},"in":"query","name":"closes_before","required":false,"description":"Closes on or before this date. Undated rolling grants remain included by default."},{"schema":{"type":"boolean","default":true},"in":"query","name":"include_rolling","required":false,"description":"Include rolling and undated opportunities."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"in":"query","name":"limit","required":false,"description":"Maximum records per page."},{"schema":{"type":"string","maxLength":4096},"in":"query","name":"cursor","required":false,"description":"Opaque continuation cursor from the previous response; valid only for the same filters."},{"schema":{"type":"string","maxLength":12000},"in":"query","name":"q","required":false,"description":"Describe the funding opportunity, for example small town park improvements."},{"schema":{"type":"string","enum":["vector","keyword"],"default":"vector"},"in":"query","name":"mode","required":false,"description":"Semantic similarity or full-text keyword matching."}],"security":[{"bearerAuth":[]}],"x-required-scope":"search","responses":{"200":{"description":"A page of ranked or deadline-ordered grants. An empty result is successful.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Find park funding by meaning","value":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null,"score":0.78}],"filtered_total":1,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}},"example2":{"summary":"Find downtown and housing keywords","value":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null,"score":0.12}],"filtered_total":42,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}},"example3":{"summary":"Browse the nearest deadlines without text ranking","value":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":1,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"400":{"description":"Invalid search mode, filter, range, date, parameter, or cursor. A cursor cannot be reused for a different search.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"Invalid search mode, filter, range, date, parameter, or cursor. A cursor cannot be reused for a different search."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"search"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"503":{"description":"The grant database or embedding service is unavailable or not configured. Retry-After is 30 seconds; keyword mode does not require the embedder.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The grant database or embedding service is unavailable or not configured. Retry-After is 30 seconds; keyword mode does not require the embedder."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}}},"description":"Find active grants by meaning or by exact keyword concepts, combined with the same structured filters as the grants list. Describe the community project, such as accessible park improvements, downtown infrastructure, or low-income housing.","x-documentation":{"overview":"Find active grants by meaning or by exact keyword concepts, combined with the same structured filters as the grants list. Describe the community project, such as accessible park improvements, downtown infrastructure, or low-income housing.","steps":["Use an API key with search scope.","Set q to a project description and use mode=vector for semantic ranking, or mode=keyword for English full-text matching.","Add any funding, date, source, or other structured filters to constrain the candidates.","Read the returned scores in context, check eligibility in the published notice, and follow meta.next_cursor with the same query and filters."],"notes":["With a nonempty q, mode defaults to vector. Vector mode orders all eligible indexed candidates by semantic similarity; it has no minimum relevance cutoff and is not a guarantee of applicant eligibility.","Keyword mode uses English full-text search over the normalized data field. Quoted phrases, OR, and a minus sign for excluding words follow PostgreSQL web-search query behavior; it is not arbitrary substring or regular-expression matching.","Keyword matching can produce fewer results than filtered_total. matching_total and meta.total_pages count only actual matches. Vector mode similarly excludes missing, outdated, or incompatible embeddings.","When q is omitted, empty, or whitespace only, no text ranking or embedder call occurs. Results are sorted by earliest closing date, with undated grants last; mode then has no ranking effect.","For text-ranked results, higher score appears first and equal scores use id ascending. Semantic and keyword scores use different scales and should not be compared across modes or unrelated searches.","The first semantic request after inactivity may take longer while the embedder wakes. If it returns 503, respect Retry-After or use keyword mode when appropriate.","Only active, unswept posted or forecasted records with no expired deadline are listed. Confirmed duplicate records are collapsed. This collection is not a historical export.","Different filter names are combined with AND. Repeated status, source, type, or category parameters match any selected value within that filter: status=posted&status=forecasted. Do not send a comma-separated list.","Dates use YYYY-MM-DD and inclusive bounds. include_rolling defaults to true, so undated records can remain in closing-date searches. Set include_rolling=false when you need only dated, non-rolling opportunities.","Award filters match overlapping award ranges; unknown award bounds are unbounded. Total-funding filters exclude unknown program totals. Amount filters are numeric US-dollar values, without currency symbols or thousands separators.","Unknown parameters, invalid dates, out-of-range limits, or a minimum greater than its maximum return 400. Discover exact agency, type, and category values using GET /v1/meta/facets.","To paginate, copy meta.next_cursor exactly into cursor and retain the same endpoint and filter values, including q and mode for search. Do not edit or decode the cursor. Changing limit is supported, but retaining it keeps page counts easier to compare.","Counts describe the current catalog and can change if a harvest completes between requests; pagination is not a fixed historical snapshot. Stop when meta.next_cursor is null.","X-Data-State reports catalog freshness on /v1 responses. X-Data-As-Of and X-Data-Age-Hours are included when known. A stale catalog can still return HTTP 200, so inspect freshness before making time-sensitive decisions.","A sleeping service can make the first request slower. Semantic search and the full dependency check may also wake the embedding service. Use a generous client timeout and respect Retry-After on 503 responses.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Find park funding by meaning","request":{"parameters":{"q":"accessible playgrounds and public park improvements for a small township","mode":"vector","limit":25}},"response":{"status":200,"body":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null,"score":0.78}],"filtered_total":1,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}}},{"title":"Find downtown and housing keywords","request":{"parameters":{"q":"\"community development\" OR \"affordable housing\"","mode":"keyword","source":["grants.gov"],"limit":25}},"response":{"status":200,"body":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null,"score":0.12}],"filtered_total":42,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}}},{"title":"Browse the nearest deadlines without text ranking","request":{"parameters":{"closes_before":"2026-12-31","include_rolling":false,"limit":25}},"response":{"status":200,"body":{"data":[{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":null}],"filtered_total":1,"matching_total":1,"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2,"next_cursor":null,"page_size":25,"total_pages":1}}}}],"responseFields":[{"name":"data","type":"object[]","description":"This page of normalized grants; an empty array is a successful response when nothing matches."},{"name":"data[].id","type":"string","description":"Internal grant identifier, encoded as a decimal string so large database identifiers remain exact. Use source and source_id for the detail endpoint."},{"name":"data[].source","type":"string","description":"Publisher identifier. The initial source is grants.gov."},{"name":"data[].source_id","type":"string","description":"Stable identifier assigned by the source. Combine with source to uniquely identify a grant."},{"name":"data[].opportunity_number","type":"string | null","description":"Publisher’s opportunity or funding notice number. This can differ from source_id."},{"name":"data[].kind","type":"string","description":"Normalized record kind: synopsis or forecast."},{"name":"data[].status","type":"string","description":"Normalized status: posted, forecasted, closed, or archived. Collection endpoints return active posted and forecasted records."},{"name":"data[].status_derived","type":"boolean","description":"True if the status was inferred during normalization rather than supplied directly."},{"name":"data[].title","type":"string","description":"Published opportunity title."},{"name":"data[].url","type":"string | null","description":"Publisher’s opportunity page. Consult this page for current application instructions and eligibility."},{"name":"data[].agency_code","type":"string | null","description":"Publishing agency code; accepted by the agency filter."},{"name":"data[].agency_name","type":"string | null","description":"Publishing agency name; the agency filter also supports a case-insensitive substring of this name."},{"name":"data[].top_level_agency_name","type":"string | null","description":"Parent agency or department, when available."},{"name":"data[].grant_type","type":"string[]","description":"Normalized funding instrument labels, such as Grant or Cooperative Agreement. Use current facet values, including capitalization, in type filters."},{"name":"data[].funding_categories","type":"string[]","description":"Publisher’s normalized funding categories. Use current facet values in category filters."},{"name":"data[].assistance_listings","type":"string[]","description":"Assistance listing identifiers associated with this opportunity."},{"name":"data[].eligible_applicants","type":"string[]","description":"Published applicant categories. Read these and the original notice before treating an opportunity as suitable for a township."},{"name":"data[].post_date","type":"string | null","description":"Publication date in YYYY-MM-DD format; null if unknown."},{"name":"data[].close_date","type":"string | null","description":"Application deadline date in YYYY-MM-DD format. Null can mean a rolling or undated opportunity; it does not guarantee applications remain open."},{"name":"data[].archive_date","type":"string | null","description":"Publisher’s archive date in YYYY-MM-DD format, if supplied."},{"name":"data[].last_updated_date","type":"string | null","description":"Publisher’s last update date in YYYY-MM-DD format, if supplied."},{"name":"data[].is_rolling","type":"boolean","description":"Whether the normalized opportunity is marked as rolling. Undated opportunities are also handled by include_rolling."},{"name":"data[].award_floor","type":"string | null","description":"Lower award amount in US dollars. Decimal database values are returned as strings; null means unknown, not zero."},{"name":"data[].award_ceiling","type":"string | null","description":"Upper award amount in US dollars, returned as a decimal string; null means unknown."},{"name":"data[].estimated_total_funding","type":"string | null","description":"Estimated funding across the whole program in US dollars, returned as a decimal string; distinct from an individual award."},{"name":"data[].expected_awards","type":"integer | null","description":"Expected number of awards, if published."},{"name":"data[].cost_sharing_required","type":"boolean | null","description":"Whether matching funds or cost sharing are required. Null means the source does not specify."},{"name":"data[].description","type":"string | null","description":"Normalized description. Treat source text as untrusted content when displaying it in your application."},{"name":"data[].data","type":"string","description":"Combined normalized grant document in Markdown, used for keyword search. It includes more context than description alone. Semantic indexing uses the title, agency, categories, eligible applicants, and description."},{"name":"data[].embedding_model","type":"string | null","description":"Embedding model associated with the stored index, when indexed. The API does not return embedding vectors."},{"name":"data[].first_seen_run","type":"string","description":"UUID of the harvest that first inserted the grant."},{"name":"data[].last_seen_run","type":"string","description":"UUID of the most recent harvest that observed the grant."},{"name":"data[].first_seen_at","type":"string","description":"UTC timestamp when the grant was first stored."},{"name":"data[].updated_at","type":"string","description":"UTC timestamp recorded for the stored grant’s update."},{"name":"data[].swept_at","type":"string | null","description":"UTC soft-deletion timestamp. Null for collection results; retained detail records may have a value and are eligible for permanent deletion six calendar months after this time."},{"name":"filtered_total","type":"integer","description":"Count after active, duplicate, and structured filters, before keyword matching or semantic index eligibility. It can exceed matching_total."},{"name":"matching_total","type":"integer","description":"Actual number of matching records across all pages, before applying the continuation cursor. This count determines total_pages."},{"name":"meta.next_cursor","type":"string | null","description":"Opaque signed continuation token. Request another page with cursor equal to this value; null means no further page."},{"name":"meta.page_size","type":"integer","description":"Requested limit (100 by default). The last page can contain fewer records."},{"name":"meta.total_pages","type":"integer","description":"Ceiling of matching_total divided by page_size; zero when no records match. It describes the full result set, not the pages remaining after a cursor."},{"name":"meta.state","type":"string","description":"Overall state of enabled sources: fresh, late, not_changing, failing, stale, never_run, unavailable, or disabled. A successful HTTP response does not by itself mean the catalog is fresh."},{"name":"meta.data_as_of","type":"string | null","description":"UTC timestamp of the oldest successful harvest among enabled sources. Null if an enabled source has never completed successfully. This is a harvest timestamp, not the publisher’s modification time."},{"name":"meta.stalest_source","type":"string | null","description":"Source with the oldest or missing successful harvest; null when no source is enabled."},{"name":"meta.age_hours","type":"number | null","description":"Hours since that successful harvest, rounded to one decimal; null if unknown."},{"name":"data[].score","type":"number (when q is nonempty)","description":"Ranking value: cosine similarity for vector mode or English full-text rank for keyword mode. Higher values rank first within this response. Omitted for no-text searches."}]}}},"/v1/grants/{source}/{source_id}":{"get":{"summary":"Read a grant, including its original published data","tags":["Grants"],"parameters":[{"schema":{"type":"string","maxLength":100},"in":"path","name":"source","required":true,"description":"Publisher identifier copied from a grant or GET /v1/sources; use grants.gov for the initial source."},{"schema":{"type":"string","maxLength":300},"in":"path","name":"source_id","required":true,"description":"Source-assigned identifier from a grant result; not internal id or opportunity_number. URL-encode this path segment."}],"security":[{"bearerAuth":[]}],"x-required-scope":"search","responses":{"200":{"description":"The active or retained grant was found.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Read a retained source record","value":{"data":{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":"2026-09-07T10:00:00.000Z","raw_data":{"OpportunitySynopsisDetail_1_0":{"OpportunityID":"EXAMPLE-123","OpportunityTitle":"Example community park improvements"}}},"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2}}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"400":{"description":"Invalid source path values.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"Invalid source path values."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"search"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"404":{"description":"No retained grant exists for this source and source_id.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"No retained grant exists for this source and source_id."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"503":{"description":"The grant database is unavailable or not configured. Retry-After is 30 seconds.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The grant database is unavailable or not configured. Retry-After is 30 seconds."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}}},"description":"Read one grant using its source identifier and include the original source record. Use this after selecting a result or when retrieving a previously saved grant that is no longer active.","x-documentation":{"overview":"Read one grant using its source identifier and include the original source record. Use this after selecting a result or when retrieving a previously saved grant that is no longer active.","steps":["Copy source and source_id from a list or search result; opportunity_number is a different field.","URL-encode each path value and send the authenticated GET request.","Inspect status, close_date, and swept_at before treating the record as active.","Use raw_data for publisher-specific details and url for the original application notice."],"notes":["The lookup includes retained, soft-deleted records and is not restricted to the active collection. Removed grants are eligible for permanent deletion six calendar months after swept_at; after deletion this endpoint returns 404.","A 404 can also mean the source or identifier never existed. There is no fallback lookup by title or opportunity number.","raw_data is publisher-specific and can change structure. Handle missing fields and render source content safely. Internal embeddings, hashes, and duplicate keys are not returned.","X-Data-State reports catalog freshness on /v1 responses. X-Data-As-Of and X-Data-Age-Hours are included when known. A stale catalog can still return HTTP 200, so inspect freshness before making time-sensitive decisions.","A sleeping service can make the first request slower. Semantic search and the full dependency check may also wake the embedding service. Use a generous client timeout and respect Retry-After on 503 responses.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Read a retained source record","request":{"parameters":{"source":"grants.gov","source_id":"EXAMPLE-123"}},"response":{"status":200,"body":{"data":{"id":"123","source":"grants.gov","source_id":"EXAMPLE-123","opportunity_number":"EXAMPLE-PARKS-2026","kind":"synopsis","status":"posted","status_derived":false,"title":"Example community park improvements","url":"https://www.grants.gov/search-results-detail/EXAMPLE-123","agency_code":"EXAMPLE","agency_name":"Example funding agency","top_level_agency_name":"Example department","grant_type":["Grant"],"funding_categories":["Community Development"],"assistance_listings":["00.000"],"eligible_applicants":["City or township governments"],"post_date":"2026-09-01","close_date":"2026-12-31","archive_date":null,"last_updated_date":"2026-09-07","is_rolling":false,"award_floor":"25000","award_ceiling":"250000","estimated_total_funding":"2000000","expected_awards":10,"cost_sharing_required":false,"description":"Illustrative funding for accessible parks and public recreation facilities.","data":"Title: Example community park improvements\nApplicants: City or township governments","embedding_model":"Qwen/Qwen3-Embedding-0.6B@97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3","first_seen_run":"00000000-0000-4000-8000-000000000002","last_seen_run":"00000000-0000-4000-8000-000000000002","first_seen_at":"2026-09-07T10:05:00.000Z","updated_at":"2026-09-07T10:05:00.000Z","swept_at":"2026-09-07T10:00:00.000Z","raw_data":{"OpportunitySynopsisDetail_1_0":{"OpportunityID":"EXAMPLE-123","OpportunityTitle":"Example community park improvements"}}},"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2}}}}],"responseFields":[{"name":"data.id","type":"string","description":"Internal grant identifier, encoded as a decimal string so large database identifiers remain exact. Use source and source_id for the detail endpoint."},{"name":"data.source","type":"string","description":"Publisher identifier. The initial source is grants.gov."},{"name":"data.source_id","type":"string","description":"Stable identifier assigned by the source. Combine with source to uniquely identify a grant."},{"name":"data.opportunity_number","type":"string | null","description":"Publisher’s opportunity or funding notice number. This can differ from source_id."},{"name":"data.kind","type":"string","description":"Normalized record kind: synopsis or forecast."},{"name":"data.status","type":"string","description":"Normalized status: posted, forecasted, closed, or archived. Collection endpoints return active posted and forecasted records."},{"name":"data.status_derived","type":"boolean","description":"True if the status was inferred during normalization rather than supplied directly."},{"name":"data.title","type":"string","description":"Published opportunity title."},{"name":"data.url","type":"string | null","description":"Publisher’s opportunity page. Consult this page for current application instructions and eligibility."},{"name":"data.agency_code","type":"string | null","description":"Publishing agency code; accepted by the agency filter."},{"name":"data.agency_name","type":"string | null","description":"Publishing agency name; the agency filter also supports a case-insensitive substring of this name."},{"name":"data.top_level_agency_name","type":"string | null","description":"Parent agency or department, when available."},{"name":"data.grant_type","type":"string[]","description":"Normalized funding instrument labels, such as Grant or Cooperative Agreement. Use current facet values, including capitalization, in type filters."},{"name":"data.funding_categories","type":"string[]","description":"Publisher’s normalized funding categories. Use current facet values in category filters."},{"name":"data.assistance_listings","type":"string[]","description":"Assistance listing identifiers associated with this opportunity."},{"name":"data.eligible_applicants","type":"string[]","description":"Published applicant categories. Read these and the original notice before treating an opportunity as suitable for a township."},{"name":"data.post_date","type":"string | null","description":"Publication date in YYYY-MM-DD format; null if unknown."},{"name":"data.close_date","type":"string | null","description":"Application deadline date in YYYY-MM-DD format. Null can mean a rolling or undated opportunity; it does not guarantee applications remain open."},{"name":"data.archive_date","type":"string | null","description":"Publisher’s archive date in YYYY-MM-DD format, if supplied."},{"name":"data.last_updated_date","type":"string | null","description":"Publisher’s last update date in YYYY-MM-DD format, if supplied."},{"name":"data.is_rolling","type":"boolean","description":"Whether the normalized opportunity is marked as rolling. Undated opportunities are also handled by include_rolling."},{"name":"data.award_floor","type":"string | null","description":"Lower award amount in US dollars. Decimal database values are returned as strings; null means unknown, not zero."},{"name":"data.award_ceiling","type":"string | null","description":"Upper award amount in US dollars, returned as a decimal string; null means unknown."},{"name":"data.estimated_total_funding","type":"string | null","description":"Estimated funding across the whole program in US dollars, returned as a decimal string; distinct from an individual award."},{"name":"data.expected_awards","type":"integer | null","description":"Expected number of awards, if published."},{"name":"data.cost_sharing_required","type":"boolean | null","description":"Whether matching funds or cost sharing are required. Null means the source does not specify."},{"name":"data.description","type":"string | null","description":"Normalized description. Treat source text as untrusted content when displaying it in your application."},{"name":"data.data","type":"string","description":"Combined normalized grant document in Markdown, used for keyword search. It includes more context than description alone. Semantic indexing uses the title, agency, categories, eligible applicants, and description."},{"name":"data.embedding_model","type":"string | null","description":"Embedding model associated with the stored index, when indexed. The API does not return embedding vectors."},{"name":"data.first_seen_run","type":"string","description":"UUID of the harvest that first inserted the grant."},{"name":"data.last_seen_run","type":"string","description":"UUID of the most recent harvest that observed the grant."},{"name":"data.first_seen_at","type":"string","description":"UTC timestamp when the grant was first stored."},{"name":"data.updated_at","type":"string","description":"UTC timestamp recorded for the stored grant’s update."},{"name":"data.swept_at","type":"string | null","description":"UTC soft-deletion timestamp. Null for collection results; retained detail records may have a value and are eligible for permanent deletion six calendar months after this time."},{"name":"data.raw_data","type":"object","description":"Original source record as stored. Field names, nesting, and types belong to the publisher and are not a stable normalized API schema."},{"name":"meta.state","type":"string","description":"Overall state of enabled sources: fresh, late, not_changing, failing, stale, never_run, unavailable, or disabled. A successful HTTP response does not by itself mean the catalog is fresh."},{"name":"meta.data_as_of","type":"string | null","description":"UTC timestamp of the oldest successful harvest among enabled sources. Null if an enabled source has never completed successfully. This is a harvest timestamp, not the publisher’s modification time."},{"name":"meta.stalest_source","type":"string | null","description":"Source with the oldest or missing successful harvest; null when no source is enabled."},{"name":"meta.age_hours","type":"number | null","description":"Hours since that successful harvest, rounded to one decimal; null if unknown."}]}}},"/v1/sources":{"get":{"summary":"Available grant data sources","tags":["Grants"],"security":[{"bearerAuth":[]}],"x-required-scope":"search","responses":{"200":{"description":"Configured sources were listed.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Discover source identifiers","value":{"data":[{"id":"grants.gov","enabled":true,"expected_interval_hours":24,"stale_after_hours":30}],"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2}}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"search"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"503":{"description":"The grant database needed for freshness metadata is unavailable or not configured. Retry-After is 30 seconds.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The grant database needed for freshness metadata is unavailable or not configured. Retry-After is 30 seconds."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}}},"description":"List configured grant sources and their freshness thresholds. Grants.gov is the only initial source; this endpoint allows clients to discover source identifiers as the catalog grows.","x-documentation":{"overview":"List configured grant sources and their freshness thresholds. Grants.gov is the only initial source; this endpoint allows clients to discover source identifiers as the catalog grows.","steps":["Use an API key with search scope.","Read data[].id and pass the chosen identifier as a source filter in list or search requests.","Use GET /v1/status when you need last harvest times or failure information."],"notes":["This is source configuration metadata, not a grant count or live call to a publisher. Disabled configured sources can still appear.","expected_interval_hours and stale_after_hours are freshness thresholds, not proof that a scheduled job has run.","X-Data-State reports catalog freshness on /v1 responses. X-Data-As-Of and X-Data-Age-Hours are included when known. A stale catalog can still return HTTP 200, so inspect freshness before making time-sensitive decisions.","A sleeping service can make the first request slower. Semantic search and the full dependency check may also wake the embedding service. Use a generous client timeout and respect Retry-After on 503 responses.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Discover source identifiers","request":{"parameters":{}},"response":{"status":200,"body":{"data":[{"id":"grants.gov","enabled":true,"expected_interval_hours":24,"stale_after_hours":30}],"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2}}}}],"responseFields":[{"name":"data[].id","type":"string","description":"Source identifier accepted by the source filter and grant detail path."},{"name":"data[].enabled","type":"boolean","description":"Whether harvesting is enabled for this source."},{"name":"data[].expected_interval_hours","type":"integer","description":"Expected successful-harvest interval in hours."},{"name":"data[].stale_after_hours","type":"integer","description":"Hours since success after which the source is stale."},{"name":"meta.state","type":"string","description":"Overall state of enabled sources: fresh, late, not_changing, failing, stale, never_run, unavailable, or disabled. A successful HTTP response does not by itself mean the catalog is fresh."},{"name":"meta.data_as_of","type":"string | null","description":"UTC timestamp of the oldest successful harvest among enabled sources. Null if an enabled source has never completed successfully. This is a harvest timestamp, not the publisher’s modification time."},{"name":"meta.stalest_source","type":"string | null","description":"Source with the oldest or missing successful harvest; null when no source is enabled."},{"name":"meta.age_hours","type":"number | null","description":"Hours since that successful harvest, rounded to one decimal; null if unknown."}]}}},"/v1/meta/facets":{"get":{"summary":"Available filter values and grant counts","tags":["Grants"],"security":[{"bearerAuth":[]}],"x-required-scope":"search","responses":{"200":{"description":"Active catalog facet values and counts were returned.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"example1":{"summary":"Build grant filter controls","value":{"data":{"agencies":[{"value":"EXAMPLE","label":"Example funding agency","count":1}],"types":[{"value":"Grant","count":1}],"categories":[{"value":"Community Development","count":1}],"statuses":[{"value":"posted","count":1}]},"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2}}}}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"401":{"description":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"A bearer API key is missing, invalid, or revoked. Supply Authorization: Bearer YOUR_API_KEY."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"403":{"description":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The authenticated key lacks the required scope. The error response includes required_scope for a scope failure.","required_scope":"search"}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"429":{"description":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The key request budget is exhausted. Wait the number of seconds in Retry-After before retrying."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}},"500":{"description":"An internal error prevented the request from completing. The response contains a safe generic error message.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"An internal error occurred."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}}}},"503":{"description":"The grant database is unavailable or not configured. Retry-After is 30 seconds.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable reason the request failed."},"required_scope":{"type":"string","description":"Required API scope, included for a missing-scope failure.","enum":["search","admin"]}}},"example":{"error":"The grant database is unavailable or not configured. Retry-After is 30 seconds."}}},"headers":{"X-Data-State":{"description":"Catalog freshness state. Authentication failures report unavailable; a 200 response may still contain stale data.","schema":{"type":"string"}},"X-Data-As-Of":{"description":"UTC timestamp of the oldest successful enabled-source harvest, when known.","schema":{"type":"string","format":"date-time"}},"X-Data-Age-Hours":{"description":"Age of that successful harvest in hours, when known.","schema":{"type":"number"}},"X-RateLimit-Limit":{"description":"Per-key request budget per minute, present after successful scope and rate-limit checks.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Whole tokens left in the current refilling key budget, when present.","schema":{"type":"integer"}},"Retry-After":{"description":"Minimum delay in seconds before retrying. Dependency-unavailable responses use 30.","schema":{"type":"integer"}}}}},"description":"Discover the actual agency, funding-instrument, category, and status values present in the active catalog, with counts. Use these values to populate filter controls rather than guessing publisher terminology.","x-documentation":{"overview":"Discover the actual agency, funding-instrument, category, and status values present in the active catalog, with counts. Use these values to populate filter controls rather than guessing publisher terminology.","steps":["Use an API key with search scope and request the facets.","Show label for agencies when present, but send value as the agency filter.","Send type, category, or status values exactly as returned. Repeat a filter parameter to select several values.","Refresh facets after catalog changes when your application needs current counts."],"notes":["Facets use the same active-record and confirmed-duplicate rules as the grants collection. They describe the whole active catalog and do not accept search filters.","A grant can have several types or categories, so counts across those groups can sum to more than the total number of grants. Missing agency data can appear as a null value or label; do not send a null value as a filter.","Agency rows group by both agency code and name. The same code can therefore appear with more than one label if the source supplies different names.","X-Data-State reports catalog freshness on /v1 responses. X-Data-As-Of and X-Data-Age-Hours are included when known. A stale catalog can still return HTTP 200, so inspect freshness before making time-sensitive decisions.","A sleeping service can make the first request slower. Semantic search and the full dependency check may also wake the embedding service. Use a generous client timeout and respect Retry-After on 503 responses.","Send Authorization: Bearer YOUR_API_KEY over HTTPS. Browser sign-in and an API key are separate credentials. API Test uses the key assigned to your account; documentation access does not grant additional endpoint permissions.","Respect X-RateLimit-Limit and X-RateLimit-Remaining when present. The per-key token budget refills continuously; a 429 response includes Retry-After in seconds. Back off and retry after that delay.","All /v1 requests, including external calls and errors, are recorded in API Logs with sensitive values redacted and large payloads capped. Logs retain today and yesterday in UTC. First-page responses are stored; successful continuation responses omit their body but retain request details and total page counts. Failed continuation requests retain their error response for debugging.","Example grants, IDs, timestamps, and keys are illustrative, not live opportunities or credentials. Replace placeholders with values from your own API responses."],"examples":[{"title":"Build grant filter controls","request":{"parameters":{}},"response":{"status":200,"body":{"data":{"agencies":[{"value":"EXAMPLE","label":"Example funding agency","count":1}],"types":[{"value":"Grant","count":1}],"categories":[{"value":"Community Development","count":1}],"statuses":[{"value":"posted","count":1}]},"meta":{"state":"fresh","data_as_of":"2026-09-07T10:05:00.000Z","stalest_source":"grants.gov","age_hours":2}}}}],"responseFields":[{"name":"data.agencies","type":"object[]","description":"Agency choices, each with value, label, and count."},{"name":"data.agencies[].value","type":"string | null","description":"Agency code to pass as the agency filter."},{"name":"data.agencies[].label","type":"string | null","description":"Agency name for display."},{"name":"data.agencies[].count","type":"integer","description":"Active grants with this agency code and name."},{"name":"data.types[].value","type":"string","description":"Exact type filter value."},{"name":"data.types[].count","type":"integer","description":"Active grants containing this value."},{"name":"data.categories[].value","type":"string","description":"Exact category filter value."},{"name":"data.categories[].count","type":"integer","description":"Active grants containing this value."},{"name":"data.statuses[].value","type":"string","description":"Exact status filter value."},{"name":"data.statuses[].count","type":"integer","description":"Active grants containing this value."},{"name":"meta.state","type":"string","description":"Overall state of enabled sources: fresh, late, not_changing, failing, stale, never_run, unavailable, or disabled. A successful HTTP response does not by itself mean the catalog is fresh."},{"name":"meta.data_as_of","type":"string | null","description":"UTC timestamp of the oldest successful harvest among enabled sources. Null if an enabled source has never completed successfully. This is a harvest timestamp, not the publisher’s modification time."},{"name":"meta.stalest_source","type":"string | null","description":"Source with the oldest or missing successful harvest; null when no source is enabled."},{"name":"meta.age_hours","type":"number | null","description":"Hours since that successful harvest, rounded to one decimal; null if unknown."}]}}}}}