Background
The API docs at mempool.space/docs are currently driven by frontend/src/app/docs/api-docs/api-docs-data.ts.
This approach has served us well to date but it has some real costs:
- Drift: When a dev adds or changes a route, they have to separately update the api-docs.
- Duplication: Example responses are copy-pasted inline, nothing is generated or validated.
- No machine-readable spec: There is no OpenAPI file that tooling can consume.
- Limited Customisation: It's hard to go beyond the current list layout for API docs without lots of wrangling.
What this new approach unlocks
Moving to inline @openapi annotations on route handlers and generating a standard OpenAPI spec enables:
- Single source of truth: The docs live next to the code making it easy to update the annotation in the same PR as any change to the route.
- Machine-readable spec: We will have an
openapi.yml that works with many tools out of the box.
- CI enforcement: A build step regenerates the spec and diffs against what's committed meaning undocumented route changes fail the build.
- Auto-generated docs page: Ultimately, the existing frontend docs page can be replaced with one driven from the generated spec instead of the hand-maintained TS file.
- Auto-generated client libraries: A valid OpenAPI spec can be used to auto-generate the mempoolJS client library instead of hand-maintaining it.
- MCP server: A spec-driven MCP server can expose the full mempool API to AI tooling, agents, and integrations.
Proposal
Put the OpenAPI definitions directly in the mempool source, as @openapi YAML blocks inside JSDoc comments above each route handler. A small collector tool (runs as a build step) scans the source and assembles everything into a single openapi.yml.
What this looks like in practice
A mempool REST API endpoint:
/**
* @openapi
* /v1/mining/pool/{slug}:
* get:
* summary: Get mining pool info
* description: Returns details about the mining pool specified by `slug`.
* tags:
* - Mining
* parameters:
* - name: slug
* in: path
* required: true
* schema:
* type: string
* responses:
* '200':
* description: Mining pool details
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/MiningPool'
*/
app.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', async (req, res) => {
// ...
});
An Esplora API endpoint:
/**
* @openapi
* /address/{address}:
* get:
* summary: Get address info
* description: Returns details about an address, including confirmed and unconfirmed balance and transaction statistics.
* tags:
* - Addresses
* parameters:
* - name: address
* in: path
* required: true
* schema:
* type: string
* responses:
* '200':
* description: Address details
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Address'
*/
app.get(config.MEMPOOL.API_URL_PREFIX + 'address/:address', async (req, res) => {
// ...
});
Schemas
Schemas (e.g. Address, Transaction, Block, MiningPool) are defined once in a central file using the same @openapi annotation pattern. Route annotations reference them via $ref, for example $ref: '#/components/schemas/Address'. The collector tool merges them into the spec's components.schemas section. This eliminates the response duplication.
Handling the /api/ vs /api/v1/ prefix
The mempool.space API is two separate APIs behind one domain:
| Layer |
Prefix |
Served by |
Endpoints |
| Esplora API |
/api/ |
electrs (Rust) |
Addresses, transactions, blocks, mempool basics |
| Mempool REST API |
/api/v1/ |
Mempool backend (Node.js, port 8999) |
Fees, mining, lightning, accelerator, services |
In production, nginx routes /api/ to electrs and /api/v1/ to the mempool backend. In simple self-hosted setups without electrs, the mempool backend reimplements the Esplora endpoints in bitcoin.routes.ts and nginx rewrites /api/ to /api/v1/ so both prefixes hit the same process.
The existing docs page already handles this simply: the template always prepends /api to the urlString, and the urlString either includes /v1/ or doesn't. The OpenAPI spec should follow the same convention with a single server (https://mempool.space/api) and the /v1/ included in the path string where it belongs:
/v1/fees/recommended (mempool REST API)
/address/{address} (Esplora API)
Alternately there is the option to use OpenAPI server-override if we feel that is superior.
Collector tool
A standalone script (no runtime dependency) that:
- Recursively finds all
.ts files under backend/src/
- Extracts YAML from
@openapi JSDoc blocks
- Merges everything onto a base template (info, servers, tags)
- Writes
openapi.yml
- Optionally validates that all
$ref pointers resolve
Run as npm run docs or in CI to catch undocumented changes.
WebSocket events
The mempool API also has WebSocket channels (live blocks, mempool stats, address tracking, transaction tracking). TBC: these may be documentable using @asyncapi annotations on the WebSocket handlers, with the collector producing a separate asyncapi.yml.
Implementation path
Phase 1: OpenAPI Spec Generation
- Add the collector tool to the mempool repo
- Define shared schemas (
Address, Transaction, Block, MiningPool, etc.) in a central file
- Add
@openapi annotations with descriptions and schema refs to each route handler
- Add a CI check that regenerates the spec and diffs against what's committed
Phase 2: Use the spec
- Replace the frontend docs page with one driven from the generated spec instead of
api-docs-data.ts
- Add
x-code-samples for mempoolJS SDK examples and curl snippets to reach feature parity with the current docs if we feel these are needed.
- Auto-generate the mempoolJS client library from the spec
- Build an MCP server from the spec for AI tooling and agent integrations
- Retire the hand-maintained
api-docs-data.ts
Background
The API docs at mempool.space/docs are currently driven by
frontend/src/app/docs/api-docs/api-docs-data.ts.This approach has served us well to date but it has some real costs:
What this new approach unlocks
Moving to inline
@openapiannotations on route handlers and generating a standard OpenAPI spec enables:openapi.ymlthat works with many tools out of the box.Proposal
Put the OpenAPI definitions directly in the mempool source, as
@openapiYAML blocks inside JSDoc comments above each route handler. A small collector tool (runs as a build step) scans the source and assembles everything into a singleopenapi.yml.What this looks like in practice
A mempool REST API endpoint:
An Esplora API endpoint:
Schemas
Schemas (e.g.
Address,Transaction,Block,MiningPool) are defined once in a central file using the same@openapiannotation pattern. Route annotations reference them via$ref, for example$ref: '#/components/schemas/Address'. The collector tool merges them into the spec'scomponents.schemassection. This eliminates the response duplication.Handling the
/api/vs/api/v1/prefixThe mempool.space API is two separate APIs behind one domain:
/api//api/v1/In production, nginx routes
/api/to electrs and/api/v1/to the mempool backend. In simple self-hosted setups without electrs, the mempool backend reimplements the Esplora endpoints inbitcoin.routes.tsand nginx rewrites/api/to/api/v1/so both prefixes hit the same process.The existing docs page already handles this simply: the template always prepends
/apito theurlString, and theurlStringeither includes/v1/or doesn't. The OpenAPI spec should follow the same convention with a single server (https://mempool.space/api) and the/v1/included in the path string where it belongs:/v1/fees/recommended(mempool REST API)/address/{address}(Esplora API)Alternately there is the option to use OpenAPI server-override if we feel that is superior.
Collector tool
A standalone script (no runtime dependency) that:
.tsfiles underbackend/src/@openapiJSDoc blocksopenapi.yml$refpointers resolveRun as
npm run docsor in CI to catch undocumented changes.WebSocket events
The mempool API also has WebSocket channels (live blocks, mempool stats, address tracking, transaction tracking). TBC: these may be documentable using
@asyncapiannotations on the WebSocket handlers, with the collector producing a separateasyncapi.yml.Implementation path
Phase 1: OpenAPI Spec Generation
Address,Transaction,Block,MiningPool, etc.) in a central file@openapiannotations with descriptions and schema refs to each route handlerPhase 2: Use the spec
api-docs-data.tsx-code-samplesfor mempoolJS SDK examples and curl snippets to reach feature parity with the current docs if we feel these are needed.api-docs-data.ts