When handing reading to Gemini is worth it
Claude Code is at its best when it is deciding, writing and editing. It is an expensive way to read forty competitor pages, a 120-page annual report or a folder of public documentation just to pull out five facts. That reading counts against the same allowance you need for the real work, and it is the first thing to hit a usage limit.
Google's Gemini API has a free tier with a long context window. A small script can send the long reading there and hand Claude only the short, checked answer. This guide builds that script, tests it, and shows how to tell Claude Code when to use it.
| Send it to Gemini | Keep it in Claude |
|---|---|
| Public web pages, docs, filings and reports you saved to disk | Anything private: client files, contracts, your inbox, your code's secrets |
| “Find every mention of X in these 30 pages” | Decisions, writing, edits and anything you will publish |
| Questions whose answer is a quote from the text | Questions that need judgment or facts that are not in the files |
The Gemini API terms say Google uses what you send to its free (“Unpaid”) services to improve its products, that “human reviewers may read, annotate, and process your API input and output,” and, in so many words: “Do not submit sensitive, confidential, or personal information to the Unpaid Services.” So this is a tool for public reading only. The script below refuses files whose names look private, but the real guard is you choosing what to send.
Get a free Gemini API key and keep it out of your project
- Sign in to Google AI Studio and create an API key. No card is needed for the free tier.
- Put the key in your shell profile or your password manager, not in a file inside the project folder. Claude Code can read files in your project; a key in a
.envthere is a key Claude can see. Our guide to keeping API keys out of Claude Code shows the safe setup. - Check your limits in AI Studio before you plan a big job. Google's rate-limits page says limits are measured as requests per minute, tokens per minute and requests per day, that “Rate limits are applied per project, not per API key,” and that daily quotas “reset at midnight Pacific time.”
export GEMINI_API_KEY="paste-your-key-here" # better: read it from your password manager
Our own project hit its free daily cap on the main Flash model after 20 requests; Google's error said limit: 20 and named the daily per-project, per-model quota. Limits differ by model and account and they change, so read yours in AI Studio. The practical lesson holds either way: put many pages into one request instead of one request per page. That is what the script does.
The script: Gemini reads, and only quoted answers come back
Save this as scripts/bulk_read.py in your project. It uses only Python's standard library, so there is nothing to install. It does three things a plain API call does not:
- It refuses private-looking files. Names containing
.env,secret,password,privateor a key extension are never sent. - It keeps only answers it can prove. Gemini must return each answer with a quote copied word for word from your files. The script searches your files for that quote and drops any answer whose quote is not there. A model can invent a sentence; it cannot invent a sentence that is also in your file.
- It falls back instead of failing. If the main model is busy (HTTP 503) or out of free quota (429), it tries the lighter model, which has its own quota. The model names end in
-latest, so the script follows Google's current model without edits.
#!/usr/bin/env python3
"""bulk_read.py - send long PUBLIC reading to Gemini's free tier; keep only answers it can quote.
Usage: python3 bulk_read.py "your question" page1.html report.txt ...
Needs: GEMINI_API_KEY in your environment (never written into this file).
"""
import html
import json
import os
import re
import sys
import urllib.error
import urllib.request
# "-latest" names follow Google's current model, so the script does not go stale.
# The lighter model is the fallback when the main one is busy (503) or out of free quota (429).
MODELS = [os.environ.get("GEMINI_MODEL", "gemini-flash-latest"), "gemini-flash-lite-latest"]
API = "https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent"
# The free tier may use what you send to improve Google's products. Refuse obvious private files.
PRIVATE = re.compile(r"\.env|secret|password|credential|private|id_rsa|\.pem$|\.key$", re.I)
def text_of(path):
raw = open(path, encoding="utf-8", errors="replace").read()
if path.lower().endswith((".html", ".htm")):
raw = re.sub(r"(?is)<(script|style)\b.*?</\1>", " ", raw)
raw = html.unescape(re.sub(r"<[^>]+>", " ", raw))
return re.sub(r"\s+", " ", raw).strip()
def norm(s):
return re.sub(r"\s+", " ", s).strip().lower()
def main():
if len(sys.argv) < 3:
sys.exit(__doc__)
key = os.environ.get("GEMINI_API_KEY")
if not key:
sys.exit("GEMINI_API_KEY is not set. See step 2 of the guide.")
question, paths = sys.argv[1], sys.argv[2:]
blocked = [p for p in paths if PRIVATE.search(p)]
if blocked:
sys.exit(f"Refusing to send files that look private: {blocked}")
docs = {p: text_of(p) for p in paths}
corpus = "\n\n".join(f"===== FILE: {p} =====\n{t}" for p, t in docs.items())
prompt = (
"Answer the question using ONLY the files below. Reply as JSON: "
'{"findings":[{"claim":"...","quote":"...","file":"..."}]}. '
"Each quote must be copied word for word from one file. "
"If the files do not answer the question, reply with an empty findings list.\n\n"
f"QUESTION: {question}\n\n{corpus}"
)
body = {"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {"responseMimeType": "application/json", "temperature": 0}}
data = None
for model in MODELS:
req = urllib.request.Request(API.format(model), data=json.dumps(body).encode(),
headers={"Content-Type": "application/json", "x-goog-api-key": key})
try:
with urllib.request.urlopen(req, timeout=180) as r:
data = json.load(r)
break
except urllib.error.HTTPError as e:
# 503 = model busy. 429 = this model's free quota is used up (each model has its own).
if e.code in (503, 429):
print(f"{model}: HTTP {e.code}, trying the next model...", file=sys.stderr)
continue
sys.exit(f"Gemini returned HTTP {e.code}: {e.read()[:600].decode(errors='replace')}")
if data is None:
sys.exit("Every model was busy or out of free quota. Daily quotas reset at midnight Pacific.")
reply = data["candidates"][0]["content"]["parts"][0]["text"]
findings = json.loads(reply).get("findings", [])
sources = [norm(t) for t in docs.values()]
kept = dropped = 0
for f in findings:
quote = norm(str(f.get("quote", "")))
if quote and any(quote in s for s in sources):
kept += 1
print(f"- {f.get('claim')}\n \"{f.get('quote')}\" [{f.get('file')}]")
else:
dropped += 1
print(f"\n{kept} answer(s) verified against your files, {dropped} dropped (quote not found).")
sys.exit(0 if kept else 2)
if __name__ == "__main__":
main()
Test it with one question that should work and one that should not
A tool that has only ever said “yes” has not been tested. Save a public page, then ask one question the page answers and one it cannot. Here is the run we did on 2026-10-11, using Google's own rate-limits page as the file:
curl -sL -o rate-limits.html https://ai.google.dev/gemini-api/docs/rate-limits python3 scripts/bulk_read.py "When do daily request quotas reset, and are limits per key or per project?" rate-limits.html python3 scripts/bulk_read.py "What is the capital of Mongolia?" rate-limits.html
gemini-flash-latest: HTTP 429, trying the next model...
- Daily request quotas reset at midnight Pacific time.
"Requests per day ( RPD ) quotas reset at midnight Pacific time." [rate-limits.html]
- Rate limits are applied per project, not per API key.
"Rate limits are applied per project, not per API key." [rate-limits.html]
2 answer(s) verified against your files, 0 dropped (quote not found).
gemini-flash-latest: HTTP 429, trying the next model...
0 answer(s) verified against your files, 0 dropped (quote not found).The first question came back with two answers, each backed by a sentence that really is on the page. The second came back empty and the script exited with code 2. That empty answer is the point: the script reported that the file does not say, instead of supplying a capital city from the model's memory. We also fed it a made-up answer with a quote that is not in the file, and it was dropped (“1 dropped”).
Tell Claude Code when to use it
Claude will not reach for the script unless you tell it to. Add a short rule to your project's CLAUDE.md:
## Bulk reading - For long PUBLIC reading (more than ~5 pages, a long report, a docs site), save the pages to research/ and run: python3 scripts/bulk_read.py "<one narrow question>" research/* - Use only answers the script marks as verified. If it returns 0, the files do not say; do not guess. - Never send private files (client documents, contracts, email, anything with keys or personal data).
If you want Claude to run it without asking each time, allow that one command in .claude/settings.json. Everything else still asks.
{
"permissions": {
"allow": ["Bash(python3 scripts/bulk_read.py *)"]
}
}Ask one narrow question per run. “Which of these pages mention a free plan, and what does each say?” returns checkable quotes. “Summarise all of this” returns prose with nothing to check it against. If you prefer a terminal tool to a script, Google's Gemini CLI can also run a prompt without opening a chat; it does not check quotes for you.
This guide is free and complete. Brainfile is the memory around it.
The script on this page is not part of Brainfile. It is yours to copy, and it works without buying anything.
Brainfile is a set of files that gives Claude Code a working memory of your project and your rules, so you stop re-explaining yourself every session. The free 30-day trial pulls the Basic set into your project: the Autopilot protocol, 10 pattern files and the weekly freshness feed. No card to start, and the files you pull stay yours.
Frequently Asked Questions
Can Claude Code use Gemini?
Not as its own model, but it can run any script you give it. A short Python script that calls the Gemini API lets Claude hand long public reading to Gemini and get back only short, quoted answers.
Is the Gemini API really free?
Google's Gemini API has a free tier with limits on requests per minute, tokens per minute and requests per day, applied per Google Cloud project. Daily quotas reset at midnight Pacific time. Check your current limits in Google AI Studio; our project's daily cap on the main Flash model was 20 requests on 2026-10-11.
Does Google use what I send to the free tier?
Yes. The Gemini API terms say content sent to the unpaid services is used to improve Google's products, that human reviewers may read it, and that you should not submit sensitive, confidential or personal information. Send public material only.
Will this save my Claude Code usage?
It moves the long reading off your Claude allowance. Claude still reads the short verified answers, so the saving is roughly the size of the documents you no longer paste into Claude.
What happens when I hit the free limit?
Gemini returns HTTP 429. The script then tries the lighter model, which has its own quota, and stops with a clear message if both are used up. Daily quotas reset at midnight Pacific time.