Skip to content

Feature: emit a single canonical origin-level OpenAPI document #158

@koala73

Description

@koala73

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:

  1. Running protoc-gen-openapiv3 N times
  2. Hand-merging or post-processing (redocly bundle, swagger-cli bundle, or custom script) to combine paths + schemas
  3. Namespacing schemas to avoid collisions across proto packages
  4. Injecting a single shared info / servers / security block that's not expressed anywhere in the protos
  5. 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.

  1. 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.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions