ADR-011 Accepted 2026-05-24

OpenAPI Surface Area Seams

The madcatalog API surface is split across multiple OpenAPI spec files, each owned by one logical domain. Payment-provider-specific endpoints live in api/{provider}.openapi.yml. The core catalog API lives in api/openapi.yml. All specs are linted by CI.

Context

The original api/openapi.yml contained all API endpoints in a single file. As the API grew to include payment infrastructure, two categories of endpoints emerged that have fundamentally different characteristics from the core catalog product:

Mixing these with /catalog, /ads, and /advertisers makes the spec harder to read and creates a false impression that Stripe is a core dependency of the catalog model rather than a replaceable payment rail.

Decision

The OpenAPI surface area is split along the following seams:

FileDomainPaths
api/openapi.yml Core madcatalog product API /catalog, /ads/*, /advertisers/*
api/stripe.openapi.yml Stripe payment integration /payments/stripe/spt, /webhooks/stripe

A redocly.yaml at the project root registers both specs. CI lints all registered specs on every commit — adding a spec file to redocly.yaml is the only step required to bring a new spec under the quality gate.

Rule for future additions: before adding any new path, identify which seam it belongs to. A new payment provider gets a new api/{provider}.openapi.yml. A new catalog or advertiser feature goes in api/openapi.yml. A path that spans seams (e.g. an MPP helper that is not Stripe-specific) belongs in the core spec.

Seam Within the Stripe Spec

Within api/stripe.openapi.yml there is a second, softer seam: POST /payments/stripe/spt is called by the client (the bidding machine), while POST /webhooks/stripe is called by Stripe's infrastructure. They share Stripe as the vendor but have different callers and trust models. They coexist in one file at this scale because both are Stripe-specific and splitting them further would add file overhead without clarity benefit. If either grows significantly, they are natural candidates for further separation.

Consequences

Positive
  • Core spec is free of payment-vendor vocabulary
  • Adding a payment rail is additive — one new file, no changes to core
  • CI lints all specs; the gate cannot be bypassed by creating an unlinted file
  • Spec ownership is clear: catalog team owns openapi.yml, payments team owns provider specs
Negative
  • Shared schemas (e.g. ErrorResponse) are referenced from both specs — not DRY at the OpenAPI level (mitigated: schemas live in static/schemas/ and are referenced by both)
  • Developers unfamiliar with the seam may add Stripe paths to the wrong file
  • Two files to update when a base URL changes

Alternatives Considered

OptionWhy not chosen
Keep everything in one spec Mixes vendor-specific infrastructure with product API; pollutes the catalog contract with Stripe vocabulary
Split webhook into its own spec Over-engineering at current scale; both webhook and SPT are Stripe-specific and cohesive as a unit
Read/write split (public vs. authenticated) Softer seam — same schemas, same base URL, same domain; tags handle the distinction without a file split