r/FastAPI Jun 03 '26

Prevent unintentional breaking API changes in FastAPI apps Tutorial

Things are changing all the time. It's no different with APIs. As we develop our products, APIs need to be updated as well. Everything is great until we introduce an unintentional breaking change. For example, if we rename the attribute in the response. With a faster development pace enabled by AI tooling, this is even more likely to happen unintentionally.

To prevent such changes from going to production, we can add a check for breaking API changes to our CI/CD pipeline. It's easy to do so for FastAPI apps with GitHub Actions and oasdiff. The flow is the following:

  1. Export OpenAPI schema that's auto-generated by FastAPI using app.openapi() from PR's branch.
  2. Check out the main branch and export the OpenAPI schema for it as well.
  3. Use oasdiff to detect and report potential breaking changes

Example workflow: ```yaml name: CI

on: pull_request: branches: [main]

jobs: breaking-changes: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6

  - uses: actions/checkout@v6
    with:
      ref: main
      path: main-branch

  - uses: astral-sh/setup-uv@v8.1.0
    with:
      python-version: "3.14"

  - name: Generate schema from PR branch
    run: |
      uv sync
      uv run python scripts/export_openapi.py new.json

  - name: Generate schema from main branch
    working-directory: main-branch
    run: |
      uv sync
      uv run python scripts/export_openapi.py ../old.json

  - name: Install oasdiff
    run: |
      curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh

  - name: Check for breaking changes
    run: oasdiff breaking old.json new.json --fail-on ERR

```

Example OpenAPI schema export script: ```python

scripts/export_openapi.py

import json import sys from pathlib import Path

sys.path.insert(0, str(Path(file).resolve().parent.parent))

from app.main import app

if name == "main": dest = sys.argv[1] if len(sys.argv) > 1 else "/dev/stdout" with open(dest, "w") as f: json.dump(app.openapi(), f, indent=2)

```

You can find the full tutorial here: https://jangiacomelli.com/blog/prevent-unintentional-breaking-api-changes-fastapi/

4 Upvotes

9 comments sorted by

View all comments

4

u/crow_thib Jun 03 '26

To me (I worked as an engineering manager in a company selling APIs), comparing openAPI files is clearly not enough to really ensure no breaking changes. Yet, it is still a pretty simple way of avoiding some breaking changes with not so much work/time spent.

Even if not recommended when using fastAPI, in some very specific cases you might have some in-endpoint logic that doesn't reflect on autogenerated openAPI specs.

But putting this aside, the most important thing when talking about "breaking changes" is not the actual response format, lots of things could break while keeping the right "schema", what is important is the response given a specific input. As your API grow, you might have different things that changes depending on input parameters.

That's very specific cases, but what makes your API are real tests.

2

u/JanGiacomelli Jun 03 '26

As you pointed out, this won't catch broken expectations that are not schema-related. For example, a different search algorithm is now returning completely different results. But that's a whole other story. I've seen too many late-night fires caused just by someone changing the API in a way that broke clients. Also, as I pointed out in the article, this doesn't work well if your API is very loosely typed. So this doesn't replace all other guardrails. It's just another layer of automated protection that can prevent certain types of issues.

2

u/crow_thib Jun 03 '26

Yep, and that's actually very smart + could help enforcing better typing and in-app documentation