API Reference¶
The Ekklesia API is a FastAPI application (src/ekklesia/api/app.py) serving five endpoints. All requests are async; the database session is managed per-request via the get_session dependency.
Interactive docs are available at http://localhost:8000/docs (Swagger UI) and http://localhost:8000/redoc.
Endpoints¶
GET /health¶
Health check. Returns immediately with no database access.
Response 200 OK:
POST /sermons¶
Starts a sermon preparation pipeline and streams progress as Server-Sent Events.
Request body:
| Field | Type | Constraints |
|---|---|---|
topic |
string |
1–2000 characters, required |
Response: text/event-stream
The response body is a stream of SSE events. Each event is a JSON-encoded StageEvent on a data: line, followed by a blank line:
data: {"stage":"planner","status":"started","elapsed_ms":0,"payload":null}
data: {"stage":"planner","status":"completed","elapsed_ms":4312,"payload":{...}}
data: {"stage":"research","status":"started","elapsed_ms":0,"payload":null}
...
data: {"stage":"brief","status":"completed","elapsed_ms":892,"payload":{"draft":{...},"markdown":"..."}}
The stream closes after brief (success) or the first failed event.
StageEvent schema:
| Field | Type | Values |
|---|---|---|
stage |
string | planner research exegesis structure critique brief error |
status |
string | started completed failed |
elapsed_ms |
integer | milliseconds since stage start |
payload |
object or null | stage output dict, or {"error":"...", "type":"..."} on failure |
sources |
array or null | RetrievalSource[] — present on research.completed; stable IDs for future inline citations |
Example — failed event:
{
"stage": "research",
"status": "failed",
"elapsed_ms": 1204,
"payload": {"error": "504 Gateway Timeout", "type": "HTTPStatusError"}
}
POST /sermons/revise¶
Applies critique recommendations to an existing sermon draft. Uses the Revision agent to produce an improved markdown document without re-running the full pipeline.
Request body:
{
"current_markdown": "# Sermon Title\n\n...",
"recommended_revisions": [
"Strengthen the application section with more concrete examples",
"Add a cross-reference to Hebrews 11 in the faith section"
]
}
| Field | Type | Constraints |
|---|---|---|
current_markdown |
string |
min length 1, required |
recommended_revisions |
string[] |
min 1 item, required |
Response 200 OK — RevisionResult:
{
"revised_markdown": "# Sermon Title\n\n...(improved)...",
"changes_summary": "Expanded the application section with three concrete daily examples. Added Hebrews 11:1-6 citation to reinforce the definition of faith."
}
This endpoint powers the "Apply Critique" button in the frontend canvas. The recommended_revisions list comes directly from the Critique agent's CritiqueReport.recommended_revisions field.
GET /lookup/strongs/{strongs_number}¶
Returns a single lexicon entry for a Strong's number.
Path parameter:
| Parameter | Pattern | Example |
|---|---|---|
strongs_number |
^[GH]\d{1,5}$ |
G4102, H539 |
Response 200 OK — LexiconEntry:
{
"strongs_number": "G4102",
"language": "greek",
"lemma": "πίστις",
"transliteration": "pistis",
"pronunciation": "pis'-tis",
"definition": "persuasion, credence; moral conviction of religious truth...",
"kjv_usage": "assurance, belief, faith, fidelity",
"derivation": "from G3982",
"occurrences": 243
}
Errors:
| Code | Condition |
|---|---|
404 Not Found |
Strong's number not in database |
422 Unprocessable Entity |
Pattern validation failure (e.g. X999) |
GET /passage/{reference:path}¶
Returns a Bible passage by reference string. The :path suffix allows slashes in the reference (e.g. multi-chapter ranges).
Path parameter:
| Parameter | Examples |
|---|---|
reference |
Romans 8:28, John 3:16-17, Rom 8:28, Genesis 1:1-2:3 |
Reference parsing is lenient — common abbreviations (Rom, Gen, Mt) are resolved to canonical book names.
Response 200 OK:
{
"passage": {
"id": "esv:45:8:28:8:28",
"translation": "esv",
"book": "Romans",
"reference": "Romans 8:28",
"pericope_title": "Future Glory",
"text": "And we know that for those who love God all things work together for good..."
},
"verses": [
{
"book": "Romans",
"chapter": 8,
"verse": 28,
"text": "And we know that for those who love God..."
}
]
}
Errors:
| Code | Condition |
|---|---|
422 Unprocessable Entity |
Reference string cannot be parsed |
404 Not Found |
Passage not found in database |
SSE client notes¶
The /sermons endpoint uses sse-starlette's EventSourceResponse. A few implementation details matter for clients:
EventSource limitation. The browser's native EventSource API does not support POST requests. The frontend uses fetch + ReadableStream instead (web/src/api/sermonStream.ts).
Heartbeat pings. sse-starlette emits : ping comment lines periodically. These keep the connection alive through proxies. Clients should silently ignore lines starting with :.
nginx buffering. In the Docker/Railway deployment nginx must have proxy_buffering off and proxy_cache off on the /sermons location or events will batch up and not stream through. See web/nginx.conf.template.
Railway edge proxy timeout. Railway's edge cuts idle SSE connections at 30s. The X-Accel-Buffering: no response header on /sermons prevents this.
CORS¶
Allowed origins are configured via the CORS_ORIGINS environment variable (comma-separated). The default covers local development:
For production, set this to the Railway frontend URL:
Logfire instrumentation¶
logfire.instrument_fastapi(app) traces every request: method, path, status code, and duration. The Logfire bootstrap in app.py must run before create_app() imports local routers so that SQLAlchemy instrumentation is active when deps.py creates the engine.