-
-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
Author: Ciprian Ștefan Pleșca
This page documents every route exposed by create_app() in src/openlongevity/api.py. The API is a FastAPI application, and its defining structural feature is that every response is explicitly labeled by mode — "persisted" for database-backed publication data, "fixture-only" for synthetic demonstration data — so a client can never mistake one for the other.
flowchart LR
subgraph Health["Health & meta"]
H1["GET /api/v1/health"]
H2["GET /api/v1/version"]
H3["GET /api/v1/health/database"]
end
subgraph Ingestion["Ingestion — requires X-Ingestion-Key"]
I1["POST /api/v1/ingestion/pubmed"]
end
subgraph Publications["Persisted publications — mode: persisted"]
P1["GET /api/v1/publications"]
P2["GET /api/v1/search"]
P3["GET /api/v1/publications/:identifier"]
P4["GET /api/v1/publications/:identifier/history"]
end
subgraph Fixtures["Synthetic demonstrations — mode: fixture-only"]
F1["GET /api/v1/evidence"]
F2["GET /api/v1/evidence/:identifier"]
F3["GET /api/v1/research-gaps"]
F4["GET /api/v1/graph"]
end
subgraph Fallback["Catch-all"]
C1["GET /api/v1/:resource -> 404 RESOURCE_UNAVAILABLE"]
end
Returns status, version, database ("ok" / "unavailable" / "not_configured"), a static provider_status of "not_probed" (the health check does not make an outbound call to any literature provider), and the standard disclaimer.
Returns the package __version__ only. No database access.
A narrower probe returning only {"status": ...}, delegating to Database.health(), which checks the alembic_version table rather than mere connectivity (see Persistence-and-Data-Governance).
The only write-triggering, network-triggering route.
| Aspect | Behavior |
|---|---|
| Auth |
X-Ingestion-Key header, compared with secrets.compare_digest against a server-configured key (OPENLONGEVITY_INGESTION_KEY or an explicit constructor argument) |
| Body |
{ "query": str (1-200 chars), "limit": int (1-25) }, extra="forbid" — unknown fields are rejected |
| No key configured | 503 INGESTION_DISABLED |
| Key missing/invalid | 401 UNAUTHORIZED |
| No database configured |
503 DATABASE_NOT_CONFIGURED (via require_repository()) |
| Invalid query |
422 INVALID_QUERY (from a ValueError in SearchQuery) |
| Upstream failure |
502 PROVIDER_UNAVAILABLE (from a ProviderError) |
| Success |
PublicationPage with mode: "persisted", each item run through PublicationRepository.save()
|
sequenceDiagram
autonumber
participant C as Client
participant A as /api/v1/ingestion/pubmed
C->>A: POST {query, limit}, header X-Ingestion-Key
A->>A: key configured? compare_digest?
A->>A: repository configured?
A->>A: source.search(SearchQuery(query, limit))
A->>A: for each Publication: repository.save(...)
A-->>C: PublicationPage(items, total, page=1, page_size=limit, mode="persisted")
Both map to the same handler. Query parameters: query (default "", max 200 chars), page (default 1, 1-10000), page_size (default 20, 1-100). Delegates to PublicationRepository.list(), which performs a bounded, autoescaped title search (see Persistence-and-Data-Governance). Returns 503 DATABASE_NOT_CONFIGURED if no database is wired.
Rejects identifiers over 160 characters with 422 INVALID_IDENTIFIER before touching the database. Returns 404 NOT_FOUND if the repository has no matching row, otherwise the full persisted publication with synthetic: false fixed by the response model's Literal[False] type.
Calls the single-publication handler first (to get its 404 behavior for free) and then returns the full ordered list of RevisionResponse entries from PublicationRepository.history().
Backed entirely by a hardcoded, process-local fixture list (currently a single SYN-001 in-vitro synthetic record). Filters by case-insensitive substring match against topic. Every item in the response has synthetic: True forced onto it and includes its computed level from EvidenceEngine.grade(). The summary is computed by EvidenceEngine.summarize().
Implementation note preserved from source: the single-identifier route calls the collection handler directly as a plain Python function with
topic=""rather than relying on FastAPI'sQuerydefault, because calling a route function outside of an actual HTTP request leaves defaultQuery(...)objects unresolved. The source code comment documenting this fix is preserved verbatim inapi.pyas a caution against a specific, non-obviousAttributeError.
Requires a non-empty topic (1-120 chars). Runs ResearchGapDetector(engine).detect(topic, fixtures) against the same synthetic fixture list used by the evidence routes — this route can only ever surface gaps within that tiny fixture set, which is a deliberate demonstration boundary, not a claim about gaps in the live literature.
Returns a fixed illustrative graph — two nodes and one edge — regardless of any query parameters. There is no query parameter on this route at all; it exists to demonstrate the response shape of a future graph-backed endpoint.
Any unmatched path under /api/v1/ returns 404 RESOURCE_UNAVAILABLE with the message "This resource is not implemented in this preview" — an explicit, honest 404 rather than a generic framework-level 404, so a client always receives the same structured error envelope.
Every error response, regardless of source, is normalized to the same shape via three exception handlers:
flowchart TD
Exc[Exception raised in a route] --> T1{"StarletteHTTPException?"}
T1 -- yes --> E1["error(code, message) with original status code"]
T1 -- no --> T2{"RequestValidationError?"}
T2 -- yes --> E2["422 error(code=INVALID_REQUEST, message)"]
T2 -- no --> T3{"SQLAlchemyError?"}
T3 -- yes --> E3["503 error(code=DATABASE_UNAVAILABLE, message)"]
This means a client integrating against the API needs to handle exactly one JSON error shape, {"error": {"code": ..., "message": ...}}, across the entire route surface.
CORS is enabled only if OPENLONGEVITY_WEB_ORIGINS is set (a comma-separated origin list), and even then only GET methods and the Accept header are allowed, with allow_credentials=False — a minimal, read-oriented cross-origin posture appropriate for a public research API with no session state.
See Analysis-Toolkit for the standalone statistical utilities that are not currently wired into any API route, or Scientific-Methodology-and-Limitations for how to correctly describe what a call to this API does and does not demonstrate.
© 2026 Ciprian Ștefan Pleșca. All rights reserved.
Licensed under the Apache License 2.0 — see LICENSE in the repository root.
OpenLongevity is research software. It does not provide medical advice, diagnosis, or clinical treatment recommendations.