Auto-generate an OpenAPI 3.1.0 specification from the official Slack Node SDK via TypeScript AST analysis.
In 2024, Slack officially deprecated and abandoned their public OpenAPI specification. The Slack Web API is complex — it relies heavily on intersection types, literal string discriminators, and dynamic RPC payloads — so standard documentation scrapers constantly generate broken or missing schemas.
This tool doesn't scrape documentation or guess at types. Using ts-morph, generate.js reads the Abstract Syntax Tree (AST) of the official @slack/web-api, @slack/bolt, and @slack/types packages. It extracts the exact TypeScript generics used by Slack's engineers and transpiles them into a pristine OpenAPI 3.1.0 JSON file.
- 100% Offline — no web scraping, no API calls
- Strictly Typed — resolves tuples, unions, intersections, literal enums, and index signatures
- Always Up-to-Date — a GitHub Action runs weekly to pull the latest SDK and regenerate the spec
The generated spec includes:
- Web API endpoints (paths) — extracted from the SDK's
Methodsclass - Events, Actions, and Shortcuts (webhooks) — extracted from Bolt's type definitions
Grab the latest generated file directly: slack-openapi-spec.json
Requires Node.js >= 18.
git clone https://github.com/ilyabrin/slack-openapi-generator.git
cd slack-openapi-generator
npm install
node generate.jsThe spec is written to ./slack-openapi-spec.json by default. Pass a path argument to write elsewhere:
node generate.js ./output/my-spec.jsonnpm test- Load SDK types — ts-morph loads all
.d.tsfiles from@slack/web-api,@slack/bolt, and@slack/types - Walk the Methods class — the SDK's
Methodsclass declares every API endpoint as a typed property. The generator recursively walks the property tree, finding leaf methods typed asMethodWithRequiredArgument<Args, Response>orMethodWithOptionalArgument<Args, Response> - Map TypeScript to OpenAPI — each type is recursively converted: literals become enums, unions become
anyOf(with optimizations like enum collapsing and common property hoisting), intersections becomeallOfwith inline object squashing, and named types become$refcomponents - Disambiguate — when two different types share the same name (e.g., bolt's
Authorizationvs web-api'sAuthorization), the registry detects the collision and assigns numbered variants - Parse events — interfaces ending in
Event,Action, orShortcutfrom the appropriate SDK directories become webhook definitions, wrapped in theEnvelopedEventenvelope where appropriate
Found a missing endpoint or a TypeScript edge case that isn't handled? Open an issue or submit a PR!