Skip to main content

apiwarden

Point it at a directory of OpenAPI specs and it serves them as live documentation — a browsable site for people, and an MCP server plus plain JSON for AI agents. Nothing to export, nothing to re-share: the specs are read from disk on every request, so whatever is on the branch is what the docs say.

Runs standalone, or mounts into an existing Django project in two lines.

pip install apiwarden
apiwarden serve ./api-docs

Why

API documentation that lives in files has to be sent to whoever needs it, again after every change. A frontend team ends up working from whichever copy they were last given, and no one can tell what moved. Serving the specs instead of sending them makes that whole problem go away: one URL, always current, for people and for the agents they work with.

The two audiences

People get a portal built on RapiDoc: a nav of colour-coded methods and URL paths, the read layout, server selection and try-it. On top of that it adds an API switcher and search across every spec, and it rescues the two things OpenAPI renderers normally drop — the info.description narrative, which becomes navigable headings, and the top-level x-* blocks where teams record rate limits, TTLs and everything else that does not fit the schema, which become tables in the overview.

An edit to a spec reaches an open page in about a second, swapped in through the renderer rather than by reloading, so nobody loses their place.

Agents get the same content as data:

Endpoint What it is
POST /mcp MCP server — list_apis, search_operations, get_operation, get_schema, get_conventions, get_spec
GET /index.json Every operation across every spec, one compact document
GET /llms.txt, /llms-full.txt The doc set as plain text
GET /openapi/<name>.json, .yaml The raw specs, byte-faithful
GET /display/<name>.json The renderer's copy: x-* folded into the overview
GET /operation/<id>.json One operation, $refs inlined
GET /revision.json Content hashes — poll to tell whether anything changed
GET /changes?since=… What moved since a baseline, breaking changes called out

Point an agent at the MCP endpoint once and it never reads a stale spec again:

{
  "mcpServers": {
    "apiwarden": { "type": "http", "url": "https://your-host/api-docs/mcp" }
  }
}

Locally, over stdio instead:

apiwarden mcp ./api-docs

Standalone

apiwarden serve ./api-docs              # http://127.0.0.1:8080, reloads as you edit
apiwarden check ./api-docs              # lint: operationIds, summaries, unresolved $refs
apiwarden build ./api-docs -o dist/     # self-contained static copy, for CI publishing
apiwarden changes ./api-docs --since v1.4.0
apiwarden snapshot ./api-docs -o baseline.json

serve takes a directory, a port, or both, in either order — a bare number is read as a port, so the common case of "same specs, different port" is short:

apiwarden serve 8081                    # default directory, port 8081
apiwarden serve ./api-docs 8081         # both
apiwarden serve ./api-docs --port 8081  # the explicit form, still fine

It watches the spec files and pushes a reload to open browsers, so editing a spec updates the page without a restart.

What changed

Being always current is only half the problem — the other half is knowing what moved. changes compares the specs against a baseline and sorts the result by what it does to a caller:

$ apiwarden changes ./api-docs --since v1.4.0
breaking  accounts POST /v1/accounts/otp/     field-added: request.device_id (required)
breaking  accounts POST /v1/accounts/otp/     response-removed: 429 no longer documented
info      accounts POST /v1/accounts/otp/     summary-changed: Send a one-time login code.
since v1.4.0: 2 breaking, 0 additive, 1 informational

Breaking is an operation or field disappearing, a new required field or parameter, a type change, an enum value being removed, or authentication being added. Additive is anything a current caller can ignore. The baseline is a git revision of the spec directory, or a snapshot file written earlier with apiwarden snapshot. --fail-on-breaking exits non-zero, so CI can gate on it.

The same comparison is on the /changes page and the list_changes MCP tool, so an agent can answer "will this break my client?" directly.

In a Django project

# settings.py
INSTALLED_APPS += ["apiwarden"]

APIWARDEN = {
    "root": BASE_DIR / "api-docs",
    "title": "Platform API",
    "servers": ["https://api.example.com"],   # what try-it should call
    "watch": DEBUG,
    "token": os.environ.get("APIWARDEN_TOKEN"), # omit for a public portal
}

# urls.py
urlpatterns += [path("api-docs/", include("apiwarden.urls"))]

That is the whole integration. The portal serves its own assets, so there is no collectstatic step, and it adds no dependency beyond PyYAML. It coexists with whatever documentation the project already has — it reads spec files and does not touch your URLs, views, or schema generation.

Two production notes:

  • watch defaults to DEBUG. Live reload holds an SSE connection open, which pins a sync worker; in production, agents poll revision.json instead.
  • "Try it" calls the API host from the browser, so that host needs to allow the docs origin in its CORS configuration.

Configuration

Settings are the same for both, via APIWARDEN, an apiwarden.toml beside the specs, or CLI flags.

Key Default Meaning
root api-docs Directory holding the specs
title derived Portal title
servers spec's own Base URLs offered for try-it
renderer vendor vendor serves the bundled RapiDoc, cdn loads it remotely
theme auto auto follows the reader's OS setting; light/dark pin it
watch False Reload when the spec files change
token None Require a shared token on every request (also APIWARDEN_TOKEN)
sources discovered Explicit {name: path} map

How specs are discovered

  1. An explicit sources map, if you set one.
  2. Otherwise a redocly.yaml next to the specs — its apis: entries are used as-is, keeping the names and ordering an existing doc set already has.
  3. Otherwise every openapi.yaml / .yml / .json below root, each named after its parent directory.

Specs are expected to be self-contained, using local #/components/... $refs.

Development

python scripts/vendor_assets.py   # download the renderer bundle
pip install -e ".[dev]"
pytest

# The browser tests are opt-in; they catch things a server-side test cannot,
# such as the renderer silently failing to load.
pip install -e ".[dev,browser]" && playwright install chromium
pytest tests/test_browser.py

tests/fixtures/sample-api/ is a small generic doc set this repo ships as its own test fixture and demo — try apiwarden serve tests/fixtures/sample-api to see it running without needing specs of your own yet. Every ./api-docs above is illustrative: point it at whatever directory holds your specs.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

apiwarden-0.2.0.tar.gz (283.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

apiwarden-0.2.0-py3-none-any.whl (273.9 kB view details)

Uploaded Python 3

File details

Details for the file apiwarden-0.2.0.tar.gz.

File metadata

  • Download URL: apiwarden-0.2.0.tar.gz
  • Upload date:
  • Size: 283.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for apiwarden-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7af542ee1a951e6589bfd5025d8e00bef5fd34ea16c510422c39f04a02f1470e
MD5 fcddd6075b9bee767874812289ccebe2
BLAKE2b-256 242aa98713af29b1a1e81bccc7bbdccf837617af1a00facf85deabda5bcb5085

See more details on using hashes here.

Provenance

The following attestation bundles were made for apiwarden-0.2.0.tar.gz:

Publisher: release.yml on t7spotter/docwarden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file apiwarden-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: apiwarden-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 273.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for apiwarden-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c6c6fe7e5590c1d50c5d53e62d91c40a80384bba3ab707d379c783a42f5253fd
MD5 3288c5caa6ec6b8c052953cc2c674321
BLAKE2b-256 0931119f453dc01271638649502440f757b89ec56344636af128cc51a7123ce2

See more details on using hashes here.

Provenance

The following attestation bundles were made for apiwarden-0.2.0-py3-none-any.whl:

Publisher: release.yml on t7spotter/docwarden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.3.0

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page