Your OpenAPI document, checked against the code that serves it.

openapi-drift reads the routes your NestJS, Express, Fastify or Spring code actually declares, compares them with the document, and fails the build when they disagree, in either direction. No AI, no network, no build of your project.

$ openapi-drift check --spec openapi.yaml --source src --prefix /api
openapi.yaml: 9 operations under /api
src: 12 routes under /api

In the code, not in the document (5):
  GET     /api/health (src/health.controller.ts:5)
  HEAD    /api/health (src/health.controller.ts:10)
  GET     /api/legacy/ping (src/health.controller.ts:18)
  DELETE  /api/users/{id} (src/users/users.controller.ts:27) — the path exists with GET, PATCH
  GET     /api/v0/ping (src/health.controller.ts:18)

In the document, not in the code (2):
  POST    /api/orders (createOrder) — the path exists with GET
  GET     /api/users/{id}/avatar (getAvatar)

Path parameters named differently (1, not a failure):
  GET     /api/orders/{id}/items: document {id}, code {orderId} (src/orders/orders.controller.ts:10)

7 drifts.

Why

Everything built on the document is only as good as the document: generated tests, client SDKs, mocks, the reviewer reading it. A document that drifts from the code makes all of them wrong without anyone noticing.

Both directions

Routes the code serves but the document does not describe, and operations the document promises but no route serves.

Read from the source

NestJS decorators, Express and Fastify calls and Spring annotations are read from the source text. Nothing is installed or started, so it runs in a second on any CI runner.

Names do not matter, routes do

/users/{id} and /Users/:userId are the same route; the different parameter names are a note, not a failure.

What is read

NestJS @Controller('users'), @Controller({ path }), @Controller(['a', 'b']) with @Get, @Post, @Put, @Patch, @Delete, @Head, @Options, @All. The global prefix (setGlobalPrefix) and URI versions are read from the code; --prefix is for a value the source does not show.
Express app.get('/x', ...), router.post(...), app.route('/x').get().post(), app.all, with routers mounted by app.use('/prefix', router) in the same file or imported from another.
Document OpenAPI 3.x, YAML or JSON. The base path of servers[0].url is applied. openapi-drift lint lists the document's own problems first.

In a pull request

The action writes the Markdown report to the job summary and exposes the number of drifts; fail-on chooses which direction fails the job.

- uses: vannt-dev/openapi-drift@v0
  with:
    spec: openapi.yaml
    source: src
    prefix: /api      # optional
    fail-on: any      # or spec, code, none

Get started

Node.js 22.18 or newer.

$ npm install -g openapi-drift
$ openapi-drift check --spec openapi.yaml --source src
$ openapi-drift routes --source src          # what the code declares
$ openapi-drift lint --spec openapi.yaml     # the document's own problems