A companion API to the Liturgical Calendar API, serving the eulogies (elogia) of the Roman Martyrology for any given liturgical day — a project of the Catholic Digital Commons Foundation, curated by a CDCF Project Committee. (The canonicalized identifiers and data standards this API builds on — CRMEDR, CLBDR, CECDR, CICLSALDR — are curated separately by the Catholic Engineering Task Force.)
Unlike the Liturgical Calendar API, which serves no copyrighted texts, a Martyrology API must serve eulogy texts — and the current texts are copyrighted (the Latin editio typica altera 2004 by the Dicastery for Divine Worship, the Italian edition by the CEI). This repository is therefore public and contains no copyrighted texts; the texts live in a private data repository (CatholicOS/martyrology-texts) that is attached only at deployment time.
What makes the split workable:
- Canonical IDs are the contract. The API's data model is keyed by the public canonical IDs of the CRMEDR (
mr:MMDD-slug). Everything except the text itself — placement, entry numbers, asterisks, countries, unnumbered-header status — is public registry data. - Public-domain editions as open sample data. Older editions of the Roman Martyrology are out of copyright — the 1914 editio typica (Latin), the 1749 Benedict XIV edition, and the old English translations. Digitized under the same data contract, they let anyone clone, run and develop the API with real data, and can even be served publicly as historical editions. Only the 2004-family texts require the private repository.
- The frontend and curation tools stay public. A curation website (review of draft IDs, cross-edition comparison, proper-eulogy management) contains no texts at rest; it displays what the API serves to authenticated curators. Authentication can reuse the CDCF shared infrastructure (Zitadel / OpenFGA, see cdcf-infra).
See docs/architecture.md for the data contract, the deployment pattern for the private data, and the API surface sketch.
Design phase. The data layer exists (CRMEDR registry public; texts extracted and keyed privately); the API surface and implementation stack are being defined. Contributions and discussion are welcome on the issues.
pip install -e '.[dev]'
cp .env.example .env # defaults serve the public-domain editions
uvicorn martyrology_api.app:create_app --factory --reload
# then e.g.:
# GET http://localhost:8000/api/v1/editions
# GET http://localhost:8000/api/v1/elogia/edition/martyrologium_romanum_1749/01/02
# docs at http://localhost:8000/docs
pytest # runs against tests/fixtures; real-data smoke tests
# activate when ../crmedr and ../clbdr are checked out
pytest --cov --cov-branch --cov-report=term-missing # with coverage reportNote: the bare /elogia/01/01 path resolves (by default) to the 2004 editio typica altera, which is not attached in a public-only clone and so returns an honest 404 — use an explicit edition/martyrologium_romanum_1749/... path or a year path (e.g. 1970/01/01) to reach the public-domain sample editions instead.
The API surface, response model, auth and curation design are specified in docs/superpowers/specs/2026-07-22-martyrology-api-v1-design.md.
Brings up Zitadel and OpenFGA locally so auth and authorization can be
exercised without production. The API itself is not containerized here —
run it on the host. The fully containerized stack lives in
martyrology-frontend.
Requires Docker with Compose v2, curl, jq, and git. scripts/smoke.sh
shells out to curl and jq (all of its JSON parsing goes through jq);
scripts/setup-stack.sh additionally needs git (it clones cdcf-infra on
the host). A missing jq otherwise surfaces as a bare "command not found"
rather than anything actionable. Ports match LiturgicalCalendar's stack, so
only one of the two can run at a time.
cp .env.example .env # 1. stack knobs
docker compose up -d # 2. infra; the store is seeded automatically
./scripts/setup-stack.sh --update-env # 3. provision Zitadel, write IDs into .env
set -a; . ./.env; set +a # 4. run the API against it
uvicorn martyrology_api.app:create_app --factory --reload
./scripts/smoke.sh # 5. verify| Service | URL | Credentials |
|---|---|---|
| Zitadel console | http://localhost:8080/ui/console | root@martyrology.localhost / RootPassword1! |
| OpenFGA API | http://localhost:8083 | Bearer OPENFGA_PRESHARED_KEY from .env |
| Adminer | http://localhost:8088 | server db, user postgres, password postgres |
| Mailpit | http://localhost:8025 | — |
ZITADEL_PORT in .env overrides the issuer origin (default 8080) when
something on the host already holds that port. Under Docker Desktop on WSL2,
the port must be free on the Windows host, not just inside WSL — ss/
netstat run from within WSL cannot see Windows-side listeners, and Docker
Desktop fails a conflicting publish silently (compose reports healthy;
only docker inspect reveals the empty port binding). To inspect the
OpenFGA store directly, use curl against the API (as scripts/smoke.sh
does) rather than a UI — there is no Playground in this stack (see below).
To grant yourself platform superuser (after signing in once, so a sub
exists — find it under Martyrology Org → Users → your user → ID):
./scripts/grant-superuser.sh <your-sub>The OIDC client secret is emitted once. setup-stack.sh captures it into
.env on the run that creates the app; a re-run cannot recover it. If .env
is lost, regenerate the secret in the Zitadel console.
OPENFGA_PRESHARED_KEY is required, not optional. Settings.authz_enabled
is false when MARTYROLOGY_OPENFGA_API_TOKEN is empty, which denies every
authorization check while the stack reports healthy.
There is no OpenFGA Playground. OpenFGA v1.15.1 panics at startup
("the playground only supports authn method 'none'") when the Playground is
enabled alongside preshared auth, and preshared auth is non-negotiable here
(Settings.authz_enabled requires a non-empty token). Inspect the store with
curl against the OpenFGA API instead — the same way production is
inspected.
The code in this repository is licensed under Apache-2.0. The eulogy texts of the 2004 editions are not part of this repository and are not redistributable; should an agreement with the rights holders be reached, texts could be served publicly without changing this architecture.