n8n API: Build a Workflow Inventory and Compare Local Snapshots
Use the n8n API to collect accessible workflows with cursor pagination, save a local ID-to-name inventory, and report added, missing and renamed entries.

Checked against the cited sources on .
Goal and prerequisites for the n8n API inventory
This tutorial uses the n8n API to collect every returned workflow page and compare a local ID-to-name snapshot with an earlier one. You need API access, permission to create a key, and a local Python environment. The n8n API documentation lists Cloud Starter, Pro and Enterprise and all self-hosted editions as supporting API access; it excludes the free trial. This availability reflects the documentation reviewed, which does not identify a deployed release.
The result covers workflows accessible to the collecting account and key, rather than proving instance-wide completeness. It detects membership and naming differences, not workflow-definition edits, execution behavior or publication state. Pages are not established as a point-in-time snapshot: changes during collection can affect the result.
We think a small inventory is a useful starting point because its review question stays clear: which workflow IDs and names changed?
Sources: n8n-docs/docs/connect/n8n-api/api-reference.md at main · n8n-io/n8n-docs · GitHub, Workflow | Connect | n8n Docs
1. Prepare the key and API root
In Settings > n8n API, create a labeled key with an expiration. Enterprise supports scoped keys: select workflow:list for collection access. Non-Enterprise keys have full access to the account's resources and capabilities; restricting this script to GET does not make those credentials read-only.
Set N8N_API_ROOT in your local environment to your instance's HTTPS address followed by /api/v1. For Cloud, use your workspace's app.n8n.cloud host; for self-hosted deployments, use your instance host and preserve any deployment-specific base path. Set N8N_API_KEY separately. Keep the key out of snapshots and shared examples.
Sources: n8n-docs/docs/connect/n8n-api/authentication.md at main · n8n-io/n8n-docs · GitHub, Authentication | Connect | n8n Docs
2. Collect all pages and save the inventory

The documented collection request is GET /api/v1/workflows. Send X-N8N-API-KEY on every request. The documented default page size is 100 and maximum is 250; this example requests 100. Omit active, name, tags and project filters. Follow this sequence:
- Request the workflow collection without inventory-narrowing filters.
- Accumulate each page's data, rejecting duplicate workflow IDs.
- Pass nextCursor unchanged as the next cursor parameter.
- Finish when nextCursor is null; reject repeated cursors or malformed responses.
- Save the complete ID-to-name mapping with sorted keys.
The following is suggested editorial example code, not a tested n8n tool. Save the next two code paragraphs, in order, as inventory.py. It uses Python's standard library. Run it with python3 inventory.py after setting the environment variables. The response-envelope detail comes partly from n8n's moving-branch maintainer reference, not a release-specific client guarantee.
import json, os from pathlib import Path from urllib.parse import urlencode from urllib.request import Request, urlopen root = os.environ['N8N_API_ROOT'].rstrip('/') key = os.environ['N8N_API_KEY'] items, seen, cursor = {}, set(), None while True: params = {'limit': 100} if cursor is not None: params['cursor'] = cursor request = Request(root + '/workflows?' + urlencode(params), headers={'X-N8N-API-KEY': key}, method='GET') with urlopen(request, timeout=30) as response: page = json.load(response) if not isinstance(page, dict) or not isinstance(page.get('data'), list) or 'nextCursor' not in page: raise ValueError('Incomplete collection: malformed page')
for workflow in page['data']: identifier, name = workflow['id'], workflow['name'] if not isinstance(identifier, str) or not isinstance(name, str) or identifier in items: raise ValueError('Incomplete collection: invalid or duplicate ID/name') items[identifier] = name cursor = page['nextCursor'] if cursor is None: break if not isinstance(cursor, str) or not cursor or cursor in seen: raise ValueError('Incomplete collection: invalid or repeated cursor') seen.add(cursor) with open('inventory.candidate.json', 'w', encoding='utf-8') as output: json.dump(items, output, sort_keys=True, ensure_ascii=False, indent=2) print('Complete collection saved as inventory.candidate.json')
The script writes only after collection completes. After a failed run, do not compare an older candidate file. Keep your previous complete baseline separately. Sorted keys make the ID-keyed mapping repeatable regardless of API ordering. Store collection metadata separately.
Sources: Authentication | Connect | n8n Docs, Workflow | Connect | n8n Docs, Pagination | Connect | n8n Docs, n8n-docs/docs/connect/n8n-api/pagination.md at main · n8n-io/n8n-docs · GitHub, n8n/.agents/skills/public-api/reference.md at master · n8n-io/n8n · GitHub, json — JSON encoder and decoder — Python 3.13.16 documentation
3. Compare snapshots and review the result

After your first successful collection, retain the candidate as inventory.previous.json. On a later successful run, save this suggested comparison code as compare.py and run python3 compare.py. It loads both JSON mappings and compares IDs before names.
import json with open('inventory.previous.json', encoding='utf-8') as source: before = json.load(source) with open('inventory.candidate.json', encoding='utf-8') as source: after = json.load(source) report = { 'added': {k: after[k] for k in sorted(after.keys() - before.keys())}, 'missing': {k: before[k] for k in sorted(before.keys() - after.keys())}, 'renamed': {k: {'before': before[k], 'after': after[k]} for k in sorted(before.keys() & after.keys()) if before[k] != after[k]} } print(json.dumps(report, sort_keys=True, ensure_ascii=False, indent=2))
These synthetic examples illustrate expected comparisons, not observed test results. A missing entry means no longer observed; review access and collection conditions before interpreting it as a deletion.
| Previous | Candidate | Report |
|---|---|---|
| a: Orders | a: Orders | All categories empty |
| a: Orders | a: Orders; b: Leads | Added b |
| a: Orders; b: Leads | a: Orders | Missing b |
| a: Orders | a: Order intake | Renamed a |
We'd assign a reviewer before replacing the baseline: a difference report is most useful when someone decides what each change means.
Sources: Workflow | Connect | n8n Docs, json — JSON encoder and decoder — Python 3.13.16 documentation
4. Troubleshoot before accepting a snapshot
The n8n API workflow reference documents these response meanings. Use the suggested checks below to investigate, rather than treating each status as a complete diagnosis.
| Status | Meaning | Suggested check |
|---|---|---|
| 400 | Invalid or malformed request | Check parameters and unchanged cursor |
| 401 | Unauthorized | Check key and authentication header |
| 403 | Forbidden | Review account access and Enterprise scopes |
Sources: Workflow | Connect | n8n Docs, n8n-docs/docs/connect/n8n-api/authentication.md at main · n8n-io/n8n-docs · GitHub, Authentication | Connect | n8n Docs, n8n-docs/docs/connect/n8n-api/pagination.md at main · n8n-io/n8n-docs · GitHub


