v0.1 — CI-ready, MIT

Catch API drift before your customers do.

drift/ci diffs what your Make or n8n integration actually calls against your live OpenAPI spec. Exits 1 on breaking drift. Drop it in CI next to your spec — every breaking change shows up the moment the spec ships, not the moment a customer files a ticket.

$bun add -D driftci
Try the demo →
5 providers
scanned in < 3s
0 deps
on your runtime
CI-native
exit codes, JSON out
real drift →
openapi.yamlTodoist.node.ts
REST v2 → 410 Gone

openapi spectodoist · v2024-09

1openapi: "3.1.0"
2info:
3 title: Todoist Sync v9
4paths:
5 /rest/v2/tasks:
6 get: ...
7 post: ...
8 /sync/v9/sync:
9 post:
10 summary: Sync resources
11 /sync/v9/projects/get_data: ...
12 /sync/v9/labels: ...

integrationn8n-nodes-base

1// Todoist node
2const baseURL = 'https://api.todoist.com',
3routing: {
4 request: {
5 method: 'GET',
6 url: '/rest/v2/tasks',
7 qs: { project_id: '={{ $value }}' }
8 },
9 output: { ... },
10}
11
12// returns: 410 Gone — endpoint retired
$ drift scan --n8n-repo n8n-io/n8n --openapi todoist.yaml
exit 1
BREAKINGendpoint_removedGET /rest/v2/tasks — not in spec. Replaced by POST /sync/v9/sync.Todoist.node.ts:142
BREAKINGendpoint_removedPOST /rest/v2/tasks — not in spec.Todoist.node.ts:198
WARNINGauth_mismatchNode uses Bearer; spec requires X-Request-Id for v9 sync.credentials/TodoistApi.ts:8
§ 01 · the pattern

The same story,
on repeat.

In every case the API provider knew the change was coming. The integration didn't. End-users found out by watching their workflows break.

Todoist
case 01
Todoist killed REST v2 → n8n's Todoist node started returning 410 Gone.
GET /rest/v2/tasks410 Gone
n8n-io/n8n#28441
LinkedIn
case 02
LinkedIn sunset API version 20250401 → every LinkedIn call returns 426 NONEXISTENT_VERSION.
POST /rest/posts426 NONEXISTENT_VERSION
n8n-io/n8n#28600
Google Ads
case 03
Google Ads deprecated v12 → Make's Google Ads modules 404 for weeks.
GET /v12/customers/…/leadForms404 NOT_FOUND
community.make.com/t/107743
Airtable
case 04
Airtable killed API keys overnight → n8n shipped an emergency commit to disable the node.
Authorization: Bearer <key>401 unauthorized
n8n-io/n8n emergency disable
Stripe
case 05
Stripe EOL'd the Sources API → still wired into the n8n Stripe node.
POST /v1/sourcesinvalid_request_error
n8n-io/n8n#18101
§ 02 · what it detects

Six classes of drift.
Four of them break things.

Path placeholders are matched positionally/users/{id} and /users/{userId} are treated as equivalent. No false positives on renamed params.

kindseveritymeaning
endpoint_removedBREAKINGModule calls a path/method missing from the spec.
method_mismatchBREAKINGSame path is in the spec, but with a different HTTP method.
missing_required_paramBREAKINGSpec requires a parameter (header, query, body field) the module never declares.
auth_mismatchBREAKINGModule's auth type differs from the spec's security scheme.
unknown_paramWARNINGModule declares a parameter the spec doesn't know about. Probably harmless, probably a hint.
unparseableINFOURL has dynamic templating — skipped, not failed. Surfaced as a warning so you can review by eye.
§ 03 · install & run

One command. Then CI does it forever.

drift/ci is a single binary with zero runtime deps on your app. Run it locally, run it on every spec commit, run it nightly against prod.

~/your-make-appbash · driftci v0.1
# 1. install once$ bun add -D driftci # 2. set MAKE_API_TOKEN in .env (Profile → API)$ cp .env.example .env # 3. scan against your live spec$ bun run scan --make --app linear --version 4 --openapi https://api.linear.app/openapi.json → scanning 23 modules…✓ 21 modules clean✗ 2 BREAKING! 1 WARNINGexit 1
§ 04 · faq

Answers, before you ask.

How is this different from openapi-diff or prism?
Those compare a spec to itself across versions. drift/ci compares the spec to what an actual integration calls — n8n nodes, Make modules. Different artifact, different parser, different findings.
Will it work with my custom n8n node?
Yes, as long as it's a standard n8n-nodes-* package and uses routing / requestDefaults for HTTP. Dynamic URLs (template strings, computed paths) are flagged as unparseable and surfaced as warnings rather than guessed at.
Does it need API access to my Make account?
Only for --make scans. Generate a read-only token at Make → Profile → API and drop it in .env. We never call the live API, just the app-definition endpoint.
What about Zapier?
Not yet. Zapier's CLI app format is well-documented, so it's coming. PRs welcome.
Can I run this on a schedule against prod?
Yes — that's the recommended setup for integrations you don't control. Nightly cron, --json output, ship to your monitoring of choice. Exit codes plug into alerting cleanly.
What does it not catch?
Nested body-schema changes. We compare top-level body field presence, not deep structural diffs. If a provider tightens a sub-field enum, we won't catch it (yet).