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.yaml ⇄ Todoist.node.ts
REST v2 → 410 Goneopenapi 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 1BREAKINGendpoint_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 patternThe same story,
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.
TTodoist
case 01Todoist killed REST v2 → n8n's Todoist node started returning 410 Gone.
GET /rest/v2/tasks→ 410 Gone
↗ n8n-io/n8n#28441LiLinkedIn
case 02LinkedIn sunset API version 20250401 → every LinkedIn call returns 426 NONEXISTENT_VERSION.
POST /rest/posts→ 426 NONEXISTENT_VERSION
↗ n8n-io/n8n#28600GGoogle Ads
case 03Google Ads deprecated v12 → Make's Google Ads modules 404 for weeks.
GET /v12/customers/…/leadForms→ 404 NOT_FOUND
↗ community.make.com/t/107743AAirtable
case 04Airtable killed API keys overnight → n8n shipped an emergency commit to disable the node.
Authorization: Bearer <key>→ 401 unauthorized
↗ n8n-io/n8n emergency disableSStripe
case 05Stripe EOL'd the Sources API → still wired into the n8n Stripe node.
POST /v1/sources→ invalid_request_error
↗ n8n-io/n8n#18101§ 02 · what it detectsSix classes of drift.
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.
| kind | severity | meaning |
|---|---|---|
| endpoint_removed | BREAKING | Module calls a path/method missing from the spec. |
| method_mismatch | BREAKING | Same path is in the spec, but with a different HTTP method. |
| missing_required_param | BREAKING | Spec requires a parameter (header, query, body field) the module never declares. |
| auth_mismatch | BREAKING | Module's auth type differs from the spec's security scheme. |
| unknown_param | WARNING | Module declares a parameter the spec doesn't know about. Probably harmless, probably a hint. |
| unparseable | INFO | URL 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).