Summary
protoc-gen-openapiv3 today emits one OpenAPI spec per service. Every consumer in the modern agent-and-API-discovery ecosystem expects one canonical OpenAPI URL per origin. A proto-first framework should make producing that URL a first-class output mode, not something users glue together with a separate bundler.
This issue documents the ecosystem shift, the concrete gap, and some possible directions. Not prescriptive — the goal is to surface the need and agree on a path.
Why one URL per origin is now table stakes
Over the last ~18 months, a stack of specs and tools has stabilised around the assumption that an origin serves a single OpenAPI document:
| Spec / tool |
What it consumes |
Notes |
| RFC 9727 — API Catalog |
service-desc rel in a linkset |
One URL per anchor, not one per microservice |
| MPP (mpp.dev) |
x-payment-info extension on operations |
Payable operations declared inline in one OpenAPI doc |
| WebMCP |
OpenAPI as the bridge to tool schemas |
Agents derive inputSchema from requestBody |
| Agent SDK codegen (openapi-typescript, openapi-python-client, stainless, etc.) |
One URL → N-language clients |
Most generators take a single input |
| Scanners (isitagentready.com, readme.com crawlers, etc.) |
Probe /openapi.json at the origin |
Industry convention |
| Postman / Insomnia / Redoc / Stoplight |
Single-file import |
UX is designed around one spec |
The common thread: origin-level documentation, not service-level. A framework that natively emits per-service files puts the user on the hook to reconcile paths, schemas, metadata blocks, and versioning across files every time the proto tree changes — exactly the kind of glue that's brittle and drifts.
Current sebuf behaviour (and why per-service is still the right default for some consumers)
One-per-service output is correct for:
- Go/TS/Swift client generation — each client lib targets one service.
- Per-service Swagger UI / Redoc — domain owners want a scoped view.
- Fast rebuild cycles — only the touched service regenerates.
So the ask isn't "remove per-service output" — it's "add an origin-bundle output alongside it."
Concrete gap
For a monorepo with N services, producing a single canonical OpenAPI file today requires:
- Running
protoc-gen-openapiv3 N times
- Hand-merging or post-processing (
redocly bundle, swagger-cli bundle, or custom script) to combine paths + schemas
- Namespacing schemas to avoid collisions across proto packages
- Injecting a single shared
info / servers / security block that's not expressed anywhere in the protos
- Re-running the merge on every proto change, and maintaining a CI drift guard
All five steps are deterministic given the input — they should live in the generator.
Proposed directions
Not prescriptive — whichever shape best fits sebuf's architecture.
A. Bundle mode in protoc-gen-openapiv3
A plugin option (e.g. bundle=true, bundle_output=openapi.json) that emits one merged spec in addition to or instead of the per-service files.
The merged output needs:
- A single
info block, configurable at build time (title, version, description, license, contact)
- A single
servers[] block
paths merged across all services
components.schemas merged with collision-safe naming (e.g. proto-package-qualified)
- Optional
tags[] generated from services for navigational grouping
B. Standalone aggregator binary
A separate protoc-gen-openapiv3-bundle (or sebuf-openapi bundle) that takes the existing per-service outputs as input and emits a merged spec. Keeps the generator simple and separates concerns.
C. A first-class config surface for bundle-level metadata
Regardless of A or B, there should be a declarative way (proto option file, YAML config, or CLI flags) to specify the info block, servers, and security schemes for the bundled output — these aren't naturally part of any single service definition.
Secondary asks (happy to split into separate issues)
These come up in the same agent-first context. Flagging for awareness; not asking for them all here.
- Generic
x-* extension annotations — let users emit x-payment-info, x-rate-limit, x-deprecated-at, etc. via proto options without hand-editing generated files. Generic slot, framework-agnostic.
- Security scheme declaration — proto-level option to declare OAuth 2.0 (with
authorization_servers / resource per RFC 9728), Bearer, or API key auth, so the bundled OpenAPI's securitySchemes + security blocks are correct without manual editing.
References
Summary
protoc-gen-openapiv3today emits one OpenAPI spec per service. Every consumer in the modern agent-and-API-discovery ecosystem expects one canonical OpenAPI URL per origin. A proto-first framework should make producing that URL a first-class output mode, not something users glue together with a separate bundler.This issue documents the ecosystem shift, the concrete gap, and some possible directions. Not prescriptive — the goal is to surface the need and agree on a path.
Why one URL per origin is now table stakes
Over the last ~18 months, a stack of specs and tools has stabilised around the assumption that an origin serves a single OpenAPI document:
service-descrel in a linksetx-payment-infoextension on operationsinputSchemafromrequestBody/openapi.jsonat the originThe common thread: origin-level documentation, not service-level. A framework that natively emits per-service files puts the user on the hook to reconcile paths, schemas, metadata blocks, and versioning across files every time the proto tree changes — exactly the kind of glue that's brittle and drifts.
Current sebuf behaviour (and why per-service is still the right default for some consumers)
One-per-service output is correct for:
So the ask isn't "remove per-service output" — it's "add an origin-bundle output alongside it."
Concrete gap
For a monorepo with N services, producing a single canonical OpenAPI file today requires:
protoc-gen-openapiv3N timesredocly bundle,swagger-cli bundle, or custom script) to combine paths + schemasinfo/servers/securityblock that's not expressed anywhere in the protosAll five steps are deterministic given the input — they should live in the generator.
Proposed directions
Not prescriptive — whichever shape best fits sebuf's architecture.
A. Bundle mode in
protoc-gen-openapiv3A plugin option (e.g.
bundle=true,bundle_output=openapi.json) that emits one merged spec in addition to or instead of the per-service files.The merged output needs:
infoblock, configurable at build time (title, version, description, license, contact)servers[]blockpathsmerged across all servicescomponents.schemasmerged with collision-safe naming (e.g. proto-package-qualified)tags[]generated from services for navigational groupingB. Standalone aggregator binary
A separate
protoc-gen-openapiv3-bundle(orsebuf-openapi bundle) that takes the existing per-service outputs as input and emits a merged spec. Keeps the generator simple and separates concerns.C. A first-class config surface for bundle-level metadata
Regardless of A or B, there should be a declarative way (proto option file, YAML config, or CLI flags) to specify the
infoblock,servers, andsecurityschemes for the bundled output — these aren't naturally part of any single service definition.Secondary asks (happy to split into separate issues)
These come up in the same agent-first context. Flagging for awareness; not asking for them all here.
x-*extension annotations — let users emitx-payment-info,x-rate-limit,x-deprecated-at, etc. via proto options without hand-editing generated files. Generic slot, framework-agnostic.authorization_servers/resourceper RFC 9728), Bearer, or API key auth, so the bundled OpenAPI'ssecuritySchemes+securityblocks are correct without manual editing.References