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:

  • POST /payments/stripe/spt — creates a Stripe Shared Payment Token. This is Stripe-specific implementation detail of the MPP payment rail. If a second payment method were added, an equivalent endpoint would appear next to it, polluting the product API with vendor names.
  • POST /webhooks/stripe — called by Stripe's servers, not by API clients. Different caller, different trust model (Stripe-Signature), different lifecycle. It is not a client API at all.

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.ymlCore madcatalog product API/catalog, /ads/*, /advertisers/*
api/stripe.openapi.ymlStripe 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 specMixes vendor-specific infrastructure with product API; pollutes the catalog contract with Stripe vocabulary
Split webhook into its own specOver-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