Run Spire
Spire is the Suite's authoring backend: a Python FastAPI service (managed with uv) that holds
everything Scribe and Slate store — documentation content and coverage, stale alerts, style guides,
the LLM gateway with its credentials, budgets and audit log, and Slate sites and builds. It keeps
all authoring data in its own authoring schema of the Apiome database, so the open-source product
never carries an authoring table or route.
Start it
cd spire
uv sync --extra dev
uv run spire-migrate # apply migrations (see below)
uv run uvicorn app.main:app --app-dir src --reload --port 8099
yarn workspace @suite/spire dev runs the same server, and yarn workspace @suite/spire migrate
the same migrations. Spire listens on port 8099; GET /health answers without authentication.
The studio reaches it through SPIRE_API_BASE_URL (http://spire:8099/v1 in Docker).
Set the variables in the environment reference. Two rules catch most failed deployments:
BETTER_AUTH_SECRETmust be byte-identical to the studio's: every call the studio makes carries a bearer signed with it, and Spire verifies againstBETTER_AUTH_SECRETand thenJWT_SECRET.NEXTAUTH_SECRETmust not be set at all. Spire refuses to start while it is present — even blank — so a pre-cutover value left in an environment file is found at boot rather than as a silent 401.
In production (APP_ENV=production) a missing secret stops the service. Outside production Spire
falls back to a built-in insecure value and logs a warning.
Migrations
Spire ships a Flyway-compatible runner. Scripts are migrations/V<nnnn>__<name>.sql, applied in
order with a CRC32 checksum recorded in public.flyway_schema_history:
uv run spire-migrate # apply what is pending
uv run spire-migrate status # what is applied, what is pending
uv run spire-migrate --dry-run # print without applying
uv run spire-migrate repair # fix a checksum after an edited script
| Migration | Creates |
|---|---|
V0002–V0004 | Tenant LLM credentials, settings, usage, budget, audit log and idempotency keys |
V0005 | Target provenance, dependency edges and documentation anchors |
V0006 | Documentation content, style guides and coverage snapshots |
V0007 | The authoring schema; earlier tables move into it |
V0008 | Stale alerts |
V0009 | Slate sites and builds |
V0010 | The style guide's tone preset |
V0011 | Scribe bulk-generation jobs |
Run the migrations against the same database SPIRE_DATABASE_URL names, after Apiome's own
migrations; Spire's schema has no foreign keys into Apiome's tables (it references versions by id),
so the two can be applied independently.
Authentication and entitlement
Every route except /health and the hosted reader surface needs Authorization: Bearer <JWT> (the
studio's service bearer, see Authentication) or an X-API-Key. The caller
must also hold the authoring entitlement, resolved user override → tenant override → plan bundle
(see Licensing). SPIRE_AUTHORING_GATING_ENABLED=false turns the flag check off
for a development database without turning authentication off.
A two-factor elevation gate exists (403 {"reason": "two_factor_required"}), but the list of routes
that require it is empty today.
LLM providers
The gateway stores one API key per provider per tenant, encrypted with
SPIRE_CREDENTIAL_ENCRYPTION_KEY; reads only ever return a …last4 fingerprint. Users set the
active provider, model and temperature from Scribe settings.
| Provider | Status |
|---|---|
anthropic | Supported |
openai | Supported |
azure_openai | Declared, no adapter yet |
ollama | Declared, no adapter yet — the studio calls Ollama directly for the canvas AI buttons (OLLAMA_BASE_URL) |
Budgets (PUT /v1/llm/{tenant}/budget), usage and the audit log are per tenant; the endpoint
groups are listed in the Spire API reference.
Tests and the corpus gate
uv run --extra dev ruff check .
uv run --extra dev pytest # DB tests skip unless SPIRE_DATABASE_URL is set
uv run --extra dev python -m app.llm.corpus
The last command is the AI evaluation corpus report: a per-format regression gate over citation and example validation that the Suite workflow runs on every push.