Public API Versioning
Internal APIs vs Public APIs
- Internal APIs are backend endpoints used by the Legion web app, browser extension, and worker. They are not treated as long-term external contracts and can evolve faster, including coordinated breaking changes when needed.
- Public APIs are customer-facing endpoints with strict contracts because customers integrate against them. They are exposed under a versioned prefix (for example:
/api/v1). - Public APIs are governed by contract checks in CI (OpenAPI snapshots + diff validation) and are implemented in dedicated public API modules (separate routes/schemas from internal APIs) to avoid unintended changes.
Types of Public API Versions
The goal is to collect incremental, non-breaking additions in a single latest public API version to avoid frequent new API versions and needing to maintain a large number of API versions.
Public API versions are divided into two types:
- Active version:
- Exactly one active version at a time.
- This is the only version allowed to receive API changes.
- Changes must be non-breaking. For example, adding endpoints or response fields is allowed; removing endpoints or adding new required request fields is not.
- Frozen versions:
- Zero or more older versions (previously active versions).
- Fully immutable: no route, schema, or contract changes are allowed.
In backend code, server/public_api/version_registry.py holds the mapping of the current ACTIVE_VERSION and the list of previously-active FROZEN_VERSIONS.
Contract Enforcement Rules (CI)
Each public API version must have a committed OpenAPI snapshot file.
scripts/public_api_contract_check.py runs on every pull request and enforces:
- Frozen versions: must match snapshot exactly (any change fails).
- Active version:
- breaking changes fail
- endpoint removals fail
- non-breaking changes require a snapshot update in the PR
Process: Creating a New Public API Version
- Choose the next version name using incremental numbering (for example,
v2afterv1). - Create a new version folder (
server/public_api/v2) and add the routes/schemas for that version. - Register the new router in
server/api.pywith prefix/api/v2. - Update
server/public_api/version_registry.py:- move the old active version into
FROZEN_VERSIONS - set
ACTIVE_VERSIONto the new version
- move the old active version into
- Generate an OpenAPI snapshot for the new version:
poetry run python scripts/public_api_generate_openapi_schema.py --version v2
- Commit the new snapshot file under
server/public_api/snapshots/. - Run contract validation:
poetry run python scripts/public_api_contract_check.py --base-ref origin/main
- Verify tests for version registry and route/snapshot presence pass.
- Add relevant tests for the new version to prevent future regressions.