Getting started
This page covers running the core-api service itself. To run this documentation site, see
Writing docs for AI agents.
Prerequisites
- mise - pins the Python version and runs project tasks
- uv - installed automatically by mise as a tool
- Docker and Docker Compose - only needed for the containerized workflow
Native workflow (mise + uv)
Run these from the core-api/ directory:
mise trust # first time only, approves this repo's mise.toml
mise install # installs the pinned Python version
mise run install # uv sync --all-groups
cp .env.example .env # point DATABASE_URL at a running Postgres instance (and Meilisearch via MEILI_URL, optional)
mise run dev # fastapi dev app/main.py, with autoreload
The API listens on http://localhost:8000. /api/v1/health returns a status payload, and the
interactive OpenAPI/Swagger UI is at /docs (raw schema at /openapi.json).
Other tasks - run mise tasks for the full list:
mise run test- pytestmise run lint/mise run format- ruffmise run typecheck- mypy (strict)mise run migrate:generate -- -m "message"/migrate:up/migrate:down- Alembicmise run db:drop/db:reseed- drop (and optionally migrate + seed) the Postgres database. Pass an optional DSN URI to overrideDATABASE_URL.
Docker Compose workflow
docker compose up --build
Set POSTGRES_PASSWORD in .env first (the compose file refuses to start without it). This starts a one-shot migrator (alembic upgrade head), the API, a PostgreSQL 16 container and Meilisearch (quick-search index; without it /api/v1/search falls back to the database). app/ and migrations/ are bind-mounted, so code edits are picked up without rebuilding the image. The API is reachable at the same http://localhost:8000 as the native workflow.
Resetting the dev database
core-api is pre-release, so migrations for a domain that hasn't shipped anywhere are edited in
place rather than accumulating a follow-up migration per change. That keeps each domain's schema
readable as one file, at the cost of one rule: an existing dev database can be behind an edited
migration with Alembic none the wiser, since the revision id didn't change. When migration files
have been edited, drop and rebuild rather than trying to upgrade:
mise run db:reseed
That drops and recreates the database named in DATABASE_URL (or .env), applies migrations,
and runs scripts/seed_data.py. Pass a DSN URI to target a different database:
mise run db:reseed postgresql+asyncpg://core_api:core_api@localhost:5432/core_api
mise run db:drop # recreate empty, without migrating or seeding
Against a Postgres whose data volume you want to wipe wholesale instead:
docker compose down -v && docker compose up -d db
mise run migrate:up
uv run python scripts/seed_data.py
uv run alembic check reports "No new upgrade operations detected" when the models and the
migration files agree - worth running after editing either.
Seeding sample data
scripts/seed_data.py writes a dataset straight through the service layer, so every insert
passes the same validation the API enforces. On a fresh database it creates the stable dev
tenants (idempotent — skipped on re-run if already seeded). Tenants reflect clients
(Tenant per client):
- Celtic (
c0000000-0000-4000-8000-000000000001) — matches the dashboard's defaultX-Tenant-ID; accounts "Celtic", "Celtic DEMO", "Celtic Migration"; the dev[email protected]login belongs to it only - HIBS, ARVO, Rovers, Charity, HIS (
c0000000-0000-4000-8000-00000000000{2..6}) — one account each; operators are members of every client tenant
uv run python scripts/seed_data.py # tenants, accounts, groups, roles, logins
uv run python scripts/seed_data.py --seed 42 # reproducible run
uv run python scripts/seed_data.py --with-subscribers # also subscribers + dependent balances/lines
Subscribers are off by default (accounts/groups are enough for most demos). Re-running does not
duplicate the stable tenants. There is no cleanup step by design — reset the database instead
(mise run db:reseed).
:::note Tests use their own database
tests/conftest.py rewrites DATABASE_URL to a <name>_test database (one per pytest-xdist
worker), so running pytest does not touch seeded dev data.
:::