The morning goes like this. Open the portal. Search "tribal". Sort by close date. Open the first eight results in eight tabs. Find the eligibility paragraph in each. Close seven tabs. Do it all again the next morning, because something new may have posted overnight and the only way to know is to look.
A saved keyword alert shortens the first half of that. It can't read the eligibility paragraph, which is where the answer lives.
Here is the list I wanted instead:
Open federal grants in the United States that tribes and tribal organizations can apply for, closing by December 7, 2026, with the funder, award ceiling, deadline and official link
One sentence, up to 30 results. It came back with 18. The whole list is here, no account needed.
Ten of the 18
| Grant | Funder | Ceiling | Closes |
|---|---|---|---|
| 2026 National Urban and Community Forestry Challenge Cost Share Grant Program | Forest Service | $750,000 | 2026-10-12 |
| Community Forest and Open Space Conservation Program | Forest Service | $600,000 | 2026-10-13 |
| Tribal Colleges and Universities Program (TCUP) | U.S. National Science Foundation | $3,500,000 | 2026-10-14 |
| U.S. Department of Justice FY26 Coordinated Tribal Assistance Solicitation Notice of Funding Opportunity | Bureau of Justice Assistance | $5,600,000 | 2026-10-15 |
| BJA FY 2026 Tribal Justice Training and Technical Assistance Program | Bureau of Justice Assistance | $1,870,000 | 2026-10-21 |
| OJJDP FY 2026 Community and Schools Youth Drug Prevention Program | Office of Juvenile Justice Delinquency Prevention | $750,000 | 2026-10-29 |
| BJA FY 2026 Tribal Civil and Criminal Legal Assistance | Bureau of Justice Assistance | $600,000 | 2026-11-06 |
| OJJDP FY 2026 Family Treatment Court Program | Office of Juvenile Justice Delinquency Prevention | $850,000 | 2026-11-16 |
| Tribal Broadband Connectivity Program | DOC NIST ERA | not published | 2026-11-17 |
| Native American Affairs: Fiscal Year 2025 Colorado River Basin Tribal Drought Resiliency Program | Bureau of Reclamation | $1,000,000 | 2026-11-20 |
Every row links to its official page on grants.gov. The list also carries a one-line reason for each match, so you can see why the Community Forest program is on a list for tribes (it names federally recognized Indian tribes as eligible applicants) without opening it.
Two things the list shows that a title search wouldn't:
- Eight of the 18 titles never say tribal, tribe, Indian or Native. The two Forest Service programs and all four OJJDP programs are on the list because of their eligibility text. A keyword on the title walks right past them.
- It is a snapshot. This run read the portal on October 8. Seven of the 18 deadlines fall between October 9 and October 15, so a copy downloaded a week later has more than a third of its rows closed or closing. That is the whole case for a list that updates itself.
Make it yours
The sentence is the only part that changes. Swap the field and the date:
- "Open federal grants for rural hospitals in the United States closing by March 31, 2027, with the funder and award ceiling"
- "Open federal grants for AI research with an award ceiling, closing by December 31, 2026"
- "Open federal grants that community colleges in Texas can apply for, with the deadline and official link"
If you'd rather have it in a script, it's five calls: read the sentence (free), get the price (free), approve that price, wait, read the rows.
import csv
import os
import time
import uuid
import requests
API = "https://api.specstream.ai"
REQUEST = (
"Open federal grants in the United States that tribes and tribal "
"organizations can apply for, closing by December 7, 2026, with the "
"funder, award ceiling, deadline and official link"
)
HOW_MANY = 30
MAX_CENTS = 500 # never approve more than $5
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SPECSTREAM_API_KEY']}"
def call(method, path, **kwargs):
for attempt in range(3): # retries are safe: the approval carries a fixed key
response = session.request(method, API + path, timeout=60, **kwargs)
if response.status_code < 500:
break
time.sleep(5)
response.raise_for_status()
return response.json()["data"]
reading = call("POST", "/v1/parse", json={"q": REQUEST, "locale": "en-US"})
print("Read as:", reading["canonical_sentence"])
quote = call("POST", "/v1/capacity/quotes", json={
"ast_hash": reading["ast_hash"],
"operation": "find",
"requested_units": HOW_MANY,
})
print(f"Costs at most ${quote['maximum_cents'] / 100:.2f}")
if quote["maximum_cents"] > MAX_CENTS:
raise SystemExit("Over budget. Ask for fewer.")
run = call("POST", "/v1/capacity/executions", json={
"quote_id": quote["quote_id"],
"quote_hash": quote["quote_hash"],
"confirm_max_cents": quote["maximum_cents"],
"idempotency_key": f"grants-{uuid.uuid4()}",
})
find_id = run["find_id"]
while call("GET", f"/v1/finds/{find_id}/rows", params={"limit": 1})["status"] in (
"queued", "planning", "running"
):
time.sleep(15)
rows, cursor = [], None
while True:
page = call("GET", f"/v1/finds/{find_id}/rows", params={"cursor": cursor} if cursor else {})
rows += page["rows"]
cursor = page["next_cursor"]
if not cursor:
break
skip = {"matches", "open"} # workspace buttons, not facts
with open("grants.csv", "w", newline="") as f:
out = csv.writer(f)
for i, row in enumerate(rows):
cells = [c for c in row["cells"] if c["column_id"] not in skip]
if i == 0:
out.writerow([c["label"] for c in cells])
out.writerow(["" if c["absence"] else c["display"] for c in cells])
print(f"{len(rows)} grants in grants.csv (list {find_id})")
pip install requests
export SPECSTREAM_API_KEY=...
python grants.py
You need an API key from specstream.ai (under Agents & API). New accounts get $10 of work free, no card. The API docs have the rest.
Stop checking
A script that runs once is the portal ritual with extra steps. The part that retires the ritual is the schedule.
Open the list in the workspace and click Watch for new ones under its name. It opens a schedule that looks for more matches every week; change the cadence anywhere from hourly to yearly, and set a maximum per run and per period so a schedule can never spend past what you meant. Then choose where new matches go: the inbox, a Slack channel, a Google Sheet or HubSpot, or your own webhook (monitor.found).
Only new and changed grants are sent. The 18 you already have stay where they are. Rechecks of kept grants cost half the rate, and the ones that stop matching are labeled, so a grant that stops fitting does not sit on your sheet looking fine. Details are in the schedules docs.
That is what replaces the eight tabs: a sheet that gets a new row when a grant that fits you posts, and a label when one stops fitting.
What it costs
10¢, 30¢ or $1 a match, depending on how many facts each row has to carry, and you see the maximum before you approve anything. A run that finds nothing is free. You pay for the 18 that matched, not the 30 you were willing to take. You start with $10 of work free, once, no card. Rates are on the pricing page.
If you write grants for a living: what is the eligibility sentence you end up reading eight tabs to find? Tell me in the comments and I'll run it and post the list.
We build SpecStream. The rows above are from a real run on October 8, 2026 (list fnd_c945x7jpcmcwn1c592334xk904). This post first appeared on the SpecStream blog.
Top comments (0)