AI-powered Bible study with commentary in the voice of the great Reformed theologians. Supports English (KJV, WEB) with EN/ID UI language toggle.
- What It Does
- Sources & Citations
- Project Structure
- API Key Safety
- Run Locally
- Run Headless (CLI / Script)
- Deploy to Fly.io
- Deploy Frontend to Vercel (optional)
- Environment Variables Reference
- Theologians & Knowledge Base
- Caching
- Guardrails
- Contributing
- You enter a Bible passage reference (e.g.
Romans 8:1-11) - The app fetches the passage text from bible-api.com
- Claude generates deep commentary in the voice and theological tradition of your chosen theologian
- A persistent chat lets you ask follow-up questions about the passage
- Supports EN/ID language toggle — all UI, commentary, and chat responses switch language
- Source: bible-api.com — a free, open REST API, no key required
- Underlying data: github.com/wldeh/bible-api
- KJV — King James Version (1611). Public domain. Crown copyright expired.
- WEB — World English Bible. Public domain. Modern English, no copyright restrictions.
All commentary is AI-generated by Claude (Anthropic) in the voice and theological tradition of the named theologian. It is not verbatim text from their published works. Each commentary panel is labelled: "AI-generated · Claude (Anthropic) · Not verbatim [Theologian]"
All documents injected into Claude's context are public domain:
| Document | Author / Body | Year |
|---|---|---|
| Westminster Confession of Faith | Westminster Assembly | 1646 |
| Westminster Shorter Catechism | Westminster Assembly | 1647 |
| Westminster Larger Catechism | Westminster Assembly | 1648 |
| Heidelberg Catechism | Ursinus & Olevianus | 1563 |
| Belgic Confession | Guido de Brès | 1561 |
| Canons of Dort | Synod of Dort | 1618–1619 |
| Second Helvetic Confession | Heinrich Bullinger | 1566 |
| Institutes of the Christian Religion | John Calvin | 1559 |
| Apostles' Creed | Ancient, c. 2nd–9th century | — |
| Nicene Creed | Council of Nicaea / Constantinople | 325, 381 AD |
| Athanasian Creed | Unknown, attributed to Athanasius | c. 5th century |
- Claude Sonnet 4 by Anthropic — anthropic.com
- API documentation: docs.anthropic.com
lectio-scriptura/
│
├── frontend/ # React + Vite frontend
│ ├── src/
│ │ ├── App.jsx # Main app component (all UI + logic)
│ │ └── main.jsx # React entry point
│ ├── index.html
│ ├── vite.config.js # Dev proxy → backend at :8000
│ └── package.json
│
├── backend/ # FastAPI backend (holds the API key)
│ ├── main.py # API proxy + health check
│ └── requirements.txt
│
├── docs/ # Additional documentation (see below)
│
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions: lint + build on push
│
├── .env.example # Template — copy to .env, fill in key
├── .gitignore # Excludes .env, node_modules, dist, etc.
├── Dockerfile # Multi-stage build for Fly.io
├── fly.toml # Fly.io deployment config
└── README.md # This file
The API key must never appear in source code, browser network requests, or git history.
Here is how this project keeps it safe:
Browser (React)
│
│ POST /api/chat ← only sends {messages, system}
│ No API key in this request
▼
FastAPI Backend (your server)
│
│ Reads ANTHROPIC_API_KEY from environment variable
│ Adds it to the Authorization header
▼
Anthropic Claude API
| ✅ Safe | ❌ Never do this |
|---|---|
Store key in .env (gitignored) |
Hard-code key in any .js or .py file |
Set as Fly.io secret (fly secrets set) |
Commit .env to git |
Read via os.getenv() in Python |
Put key in VITE_ env vars (these are public) |
| Rotate key if accidentally exposed | Share key in Slack, email, or GitHub issues |
- Go to console.anthropic.com immediately
- Revoke the exposed key
- Generate a new key
- Update your
.envand Fly.io secrets - Audit your git history — if the key was committed, consider the repo compromised
- Python 3.11+
- Node.js 20+
- An Anthropic API key from console.anthropic.com
git clone https://github.com/your-username/lectio-scriptura.git
cd lectio-scripturacp .env.example .envOpen .env and replace sk-ant-your-key-here with your real key:
ANTHROPIC_API_KEY=sk-ant-api03-...Do not commit this file. It is already in .gitignore.
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
uvicorn main:app --reload --port 8000You should see:
INFO: Uvicorn running on http://127.0.0.1:8000
INFO: Application startup complete.
Test it:
curl http://localhost:8000/health
# → {"status":"ok"}Open a new terminal:
cd frontend
npm install
npm run devYou should see:
VITE v5.x.x ready in xxx ms
➜ Local: http://localhost:5173/
Open http://localhost:5173 in your browser.
- Type a passage reference, e.g.
John 3:16orRoma 8:1-11 - Select a translation (KJV or WEB)
- Select a theologian
- Press Study
- Read the commentary, then ask follow-up questions in the chat
If you want to use the backend without the browser — e.g. for scripting, automation, or generating commentary from the terminal — you can call the API directly.
cd backend
source venv/bin/activate
uvicorn main:app --port 8000curl -X POST http://localhost:8000/api/chat \
-H "Content-Type: application/json" \
-d '{
"system": "You are John Calvin writing a biblical commentary. Be precise and Reformed.",
"messages": [
{
"role": "user",
"content": "Write a short commentary on John 3:16."
}
]
}'import requests
response = requests.post("http://localhost:8000/api/chat", json={
"system": "You are Matthew Henry writing a pastoral commentary.",
"messages": [
{"role": "user", "content": "Comment on Psalm 23."}
]
})
print(response.json()["text"])# headless_study.py
import requests
PASSAGE = "Romans 8:1-11"
THEOLOGIAN = "Charles Spurgeon"
SYSTEM = f"You are {THEOLOGIAN} writing a sermon commentary. Be vivid and applicational."
# Fetch passage from Bible API
import urllib.parse
bible = requests.get(f"https://bible-api.com/{urllib.parse.quote(PASSAGE)}?translation=kjv").json()
passage_text = bible["text"]
# Get commentary
result = requests.post("http://localhost:8000/api/chat", json={
"system": SYSTEM,
"messages": [{"role": "user", "content": f"Comment on {PASSAGE}:\n\n{passage_text}"}]
})
print(f"=== {THEOLOGIAN} on {PASSAGE} ===\n")
print(result.json()["text"])python headless_study.pyThis is the recommended production path — single Fly.io app serves both backend and frontend.
- Fly CLI installed
- A Fly.io account (free tier works for personal use)
cd frontend
npm install
npm run build
cd ..This generates frontend/dist/ which the Dockerfile copies into the image.
fly launchWhen prompted:
- App name:
lectio-scriptura(or your preferred name) - Region:
syd(Sydney, closest to Melbourne) - Do NOT deploy yet
fly secrets set ANTHROPIC_API_KEY=sk-ant-your-key-hereThis stores the key encrypted in Fly's secret store. It is injected as an environment variable at runtime. It never appears in the Dockerfile or source code.
fly deployfly openYour app is live at https://lectio-scriptura.fly.dev (or your custom domain).
cd frontend && npm run build && cd ..
fly deployIf you prefer to host the frontend separately on Vercel and the backend on Fly.io:
Note your backend URL, e.g. https://lectio-scriptura.fly.dev
cd frontend
npx vercelWhen prompted, set the environment variable:
VITE_API_BASE_URL = https://lectio-scriptura.fly.dev
This tells the frontend where to send API requests.
In backend/main.py, update the allow_origins list:
allow_origins=["https://your-app.vercel.app"],Redeploy the backend: fly deploy
| Variable | Where | Required | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
Backend only | ✅ Yes | Your Anthropic API key. Never in frontend. |
VITE_API_BASE_URL |
Frontend build | ❌ No | Backend URL for production. Empty = same origin. |
All commentaries are AI-generated in the voice of each theologian, not verbatim quotes.
| Theologian | Tradition | Style |
|---|---|---|
| John Calvin (1509–1564) | Reformed, Geneva | Precise, exegetical, covenantal |
| Matthew Henry (1662–1714) | Puritan, Presbyterian | Pastoral, devotional, applicational |
| N.T. Wright (1948–present) | Anglican, New Perspective | Narrative, historical-critical |
| Charles Spurgeon (1834–1892) | Particular Baptist | Preacher, vivid, evangelistic |
| Martin Luther (1483–1546) | Lutheran | Law/gospel, direct, earthy |
| Thomas Aquinas (1225–1274) | Roman Catholic, Scholastic | Systematic, logical, patristic |
Claude is given a detailed summary of eleven confessional documents as part of its
system prompt on every request (see App.jsx → REFORMED_KNOWLEDGE).
It is instructed to cite these documents specifically by chapter, article, or
question number when relevant.
The app uses a three-tier in-memory cache to reduce API calls and speed up repeated requests. All caches are in-memory (cleared on server restart) — no Redis or external infrastructure needed.
Full Claude API responses are cached by request hash. Duplicate Study requests (same passage, theologian, and language) skip the Claude API call entirely — the cached commentary is returned instantly.
The static portion of the system prompt (~10K chars: theologian voice, Reformed
confessional knowledge base, accuracy rules) is marked for Anthropic's prompt caching
via cache_control: ephemeral. Claude reuses the cached prefix across requests with
the same theologian, reducing input token cost by ~90% for the cached portion.
The frontend inserts a [CACHE_BREAK] delimiter between the static prefix and the
passage-specific suffix. The backend splits the system prompt on this marker and
passes it to Claude as a list of content blocks.
Scripture text fetched from bible-api.com is cached via the backend proxy at
GET /api/bible. Bible passages never change, so a long TTL is safe. The frontend
calls this backend endpoint instead of bible-api.com directly.
Browser → Backend → RESPONSE_CACHE (6h) ── hit? → return cached response
│
└── miss? → Claude API ← PROMPT_CACHE (ephemeral)
│
└── store in RESPONSE_CACHE → return
Browser → Backend → BIBLE_CACHE (24h) ── hit? → return cached passage
│
└── miss? → bible-api.com → store → return
The app enforces strict scope on chat responses:
Allowed:
- Questions about the current Bible passage
- Christian theology, doctrine, biblical interpretation
- Reformed/Presbyterian confessional standards
- Church history, Christian ethics, spiritual application
- Philosophy or history if meaningfully connected to Scripture
Declined (guardrail triggered):
- Sports, cooking, programming, entertainment, politics, general trivia
- Any question clearly unrelated to the Bible or Christian faith
When declined, a shield icon and a gracious message are shown in the chat. The system never returns a cold error — always a pastoral redirect.
Pull requests welcome. Please:
- Keep
.envout of commits (check.gitignore) - Run
ruff check backend/before pushing Python changes - Run
npm run buildbefore pushing frontend changes to verify it compiles - Document any new theologians added with era and tradition in this README
Soli Deo Gloria