{"slug": "show-hn-easydocs-generate-openapi-docs-from-real-traffic", "title": "Show HN: EasyDocs – generate OpenAPI docs from real traffic", "summary": "EasyDocs is a new open-source tool that generates accurate OpenAPI 3.0 specs from real API traffic using an AI model, running entirely on the user's machine with no data sent externally. It supports local-first operation, PII redaction, and framework-native accuracy, and includes features like version history, spec diffing, and a GitHub Action for PR comments.", "body_md": "**The API spec you can actually trust — generated on your machine, from real traffic.**\n\n**Project page:** [rubenglez.dev/easydocs](https://rubenglez.dev/easydocs)\n\nEasyDocs generates accurate, up-to-date OpenAPI 3.0 specs from your API's real traffic, running entirely on your machine with nothing sent to anyone. Add one line; no spec files to write, no annotations to maintain. Your docs describe what the API actually does, not what you thought it did when you last touched the YAML. (Under the hood an AI model turns observed requests and responses into the spec — richer than mechanical type-merging — and you stay in control of what ships.)\n\n**Local-first**— your traffic never leaves your machine. No cloud, fully self-hostable.** Works fully offline**— point it at a local Ollama model; no API key required, and no model vendor ever sees your data.** PII-safe by default**— secrets and personal data (passwords, tokens, emails, card numbers) are detected and redacted before the payload reaches a hosted AI provider, and flagged in the docs.**Open-source & free**— no SaaS lock-in, no paywalled features.** Framework-native accuracy**— adapters capture true route templates (`/users/:id`\n\n), not the concrete URLs (`/users/123`\n\n) a proxy sees.**Accurate, not mechanical**— an AI model reads real requests and responses to produce richer specs (real descriptions, detected auth schemes), not the bare schema a type-merger infers. Accuracy is measured against hand-written ground truth — see the[benchmark](/RubenGlez/easydocs/blob/main/BENCHMARK.md).\n\nRoute your requests through the EasyDocs proxy. Nothing to install in your project.\n\n```\nnpx @easydocs/cli proxy --project=my-api --port=3999\n```\n\nThen send requests through the proxy:\n\n```\nhttp://localhost:3999?target=https://api.example.com/users\nnpm install @easydocs/express\njs\nimport { easydocs } from \"@easydocs/express\";\n\napp.use(easydocs({ project: \"my-api\" }));\n// all your existing routes stay the same\nnpm install -D @easydocs/dashboard\nnpx @easydocs/cli dashboard\n# → http://localhost:4999\n```\n\nThe dashboard also tracks version history: each endpoint records how its spec evolved over time, with a field-level diff between any two versions.\n\nOr export to a file:\n\n```\nnpx @easydocs/cli export > openapi.json\nnpx @easydocs/cli export --yaml > openapi.yaml\n```\n\nCommit your exported spec (`openapi.json`\n\n) and let EasyDocs comment the field-level\nchanges on every PR. Diff two spec files directly:\n\nThe diff is grouped by endpoint and each change is tagged breaking / additive / non-breaking, so a removed response field reads differently from a description tweak.\n\n```\nnpx @easydocs/cli diff old.json new.json                    # human-readable summary\nnpx @easydocs/cli diff old.json new.json --markdown         # PR-comment Markdown\nnpx @easydocs/cli diff old.json new.json --fail-on=breaking # exit 3 on a breaking change\n```\n\n`--fail-on`\n\naccepts `none`\n\n(default, never fails), `breaking`\n\n(fail on any breaking\nchange), or `any`\n\n(fail on any change at all).\n\nOr drop in the GitHub Action — it diffs the committed spec against the base branch and posts a sticky comment (updated in place on each push):\n\n```\n# .github/workflows/easydocs.yml\nname: API spec diff\non: pull_request\npermissions:\n  pull-requests: write\njobs:\n  spec-diff:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: RubenGlez/easydocs@v1\n        with:\n          spec: openapi.json\n          fail-on: breaking   # optional; default 'none' (comment-only)\n```\n\nBy default the check is informational — it comments the diff and never fails the\nbuild. Set `fail-on: breaking`\n\n(or `any`\n\n) to turn it into a review gate that fails\nthe PR when the spec changes cross that threshold; the comment is still posted.\n\n`diff`\n\ncompares two spec *files*. ** drift compares your committed spec against\nwhat your API actually does** — the spec EasyDocs derives from real traffic. It\nanswers \"is my spec still true?\", not \"did my spec change?\".\n\n```\nnpx @easydocs/cli drift openapi.json              # against locally captured traffic\nnpx @easydocs/cli drift openapi.json --markdown   # PR-comment Markdown\nnpx @easydocs/cli drift committed.json live.json  # or compare two files directly\n```\n\nIt surfaces three kinds of divergence: endpoints and fields **observed in traffic\nbut missing from your spec** (your docs are stale), things **documented but never\nobserved** (dead or un-exercised), and values where your spec **contradicts**\nreality. This is the one check only EasyDocs can run — it's the only tool holding\nboth the committed spec and the live traffic at the same time. Like `diff`\n\n, it's\ninformational and never fails the build.\n\n**See it in one command** — no setup, no API key. Clone this repo and run:\n\n```\npnpm demo:drift\n```\n\nIt seeds a sample scenario (observed traffic vs. a drifted `openapi.json`\n\n) and\nprints the report, so you can see all three kinds of drift before wiring up your\nown API.\n\n- Middleware (or proxy) intercepts every request and response\n- A background queue feeds the captured data to an AI model — nothing blocks your request\n- The AI generates or updates an OpenAPI 3.0 Operation object for that endpoint\n- Response-shape hashing skips re-processing when the structure hasn't changed\n- Specs are stored in SQLite (default) or Postgres\n- The dashboard reads from that database and renders live docs\n\n| Package | Framework |\n|---|---|\n`@easydocs/express` |\n\n`@easydocs/fastify`\n\n`@easydocs/hono`\n\n`@easydocs/nestjs`\n\n`@easydocs/nextjs`\n\n`@easydocs/h3`\n\n`@easydocs/elysia`\n\n`@easydocs/trpc`\n\n`@easydocs/cli`\n\nSet one environment variable:\n\n```\n# OpenAI\nOPENAI_API_KEY=sk-...\n\n# Anthropic\nANTHROPIC_API_KEY=sk-ant-...\n\n# DeepSeek\nDEEPSEEK_API_KEY=sk-...\n\n# Ollama (local, no key needed)\n# configure in code: easydocs({ ai: { provider: 'ollama' } })\n```\n\nEasyDocs auto-detects the provider from your environment. If no key is set, it falls back to Ollama at `localhost:11434`\n\n.\n\n```\neasydocs({\n  project: \"my-api\", // separate spec per service, default: 'default'\n  ai: {\n    provider: \"openai\", // 'openai' | 'anthropic' | 'ollama' | 'deepseek'\n    model: \"gpt-4o\",\n    apiKey: \"...\", // optional, falls back to env vars\n  },\n  storage: {\n    type: \"sqlite\", // 'sqlite' | 'postgres'\n    url: \"file:./docs.sqlite\",\n  },\n  capture: {\n    ignoreRoutes: [\"/health\", \"/metrics\"],\n    includePaths: [\"/api\"],\n  },\n  privacy: {\n    enabled: true, // on by default; detect & redact PII/secrets\n    offline: false, // strict local-first: only ever use a local Ollama model\n    placeholder: \"[REDACTED]\", // value substituted for sensitive fields\n    allowlist: [\"public_token\"], // key names never to flag\n    customRules: {\n      keyNames: [\"internalRef\"], // extra sensitive key names\n      valuePatterns: [\"^INT-\\\\d+$\"], // extra regex value matchers\n    },\n  },\n  dashboard: {\n    autoStart: true, // spawn dashboard on first capture (dev only)\n    port: 4999,\n  },\n});\n```\n\nDetection is deterministic and fully offline. Values are redacted before being sent\nto a **hosted** provider (OpenAI/Anthropic/DeepSeek); with local Ollama nothing leaves\nthe machine, so real values are kept for accuracy. Flagged fields are marked in the\nspec with `x-easydocs-sensitive`\n\nand shown with a badge in the dashboard.\n\nFor regulated or air-gapped environments, set `privacy.offline: true`\n\nfor a hard\nguarantee: EasyDocs pins itself to a local Ollama model, **ignores any hosted API\nkeys in the environment**, and refuses to start if a hosted provider is explicitly\nconfigured. Nothing captured can ever reach a third-party service — redaction becomes\nmoot because no payload leaves the machine at all.\n\nScope traffic from different services to separate specs:\n\n```\n// service-a\napp.use(easydocs({ project: \"users-service\" }));\n\n// service-b\napp.use(easydocs({ project: \"orders-service\" }));\n```\n\nSwitch between projects in the dashboard or scope the export:\n\n```\nnpx @easydocs/cli export --project=users-service > users.json\n```\n\nMIT", "url": "https://wpnews.pro/news/show-hn-easydocs-generate-openapi-docs-from-real-traffic", "canonical_source": "https://github.com/RubenGlez/easydocs", "published_at": "2026-07-17 10:34:38+00:00", "updated_at": "2026-07-17 10:51:16.007557+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "artificial-intelligence"], "entities": ["EasyDocs", "OpenAPI", "Ollama", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/show-hn-easydocs-generate-openapi-docs-from-real-traffic", "markdown": "https://wpnews.pro/news/show-hn-easydocs-generate-openapi-docs-from-real-traffic.md", "text": "https://wpnews.pro/news/show-hn-easydocs-generate-openapi-docs-from-real-traffic.txt", "jsonld": "https://wpnews.pro/news/show-hn-easydocs-generate-openapi-docs-from-real-traffic.jsonld"}}