ADR-007 Accepted 2026-05-20

REST API as Canonical Interface

HTTP REST protected by MPP is the foundational interface; MCP, the catalog UI, and future SDKs all build on top of it.

Context

madcatalog needs a canonical programmatic interface that is accessible to any HTTP client in any language, machine-authenticated via MPP, and the single source of truth that the MCP server, catalog UI, and advertiser profiles all read from.

The interface must also handle two audiences at the same URL for advertiser profiles: humans expect HTML, machines expect JSON. Forcing separate URLs creates duplicate canonical state and breaks Open Graph sharing.

Decision

HTTP REST API (4 endpoints, described in api/openapi.yml) protected by MPP (HTTP 402 challenge-response + Stripe Shared Payment Token). The full endpoint list is: GET /catalog, POST /ads/bid, POST /payments/stripe/spt, POST /webhooks/stripe, and PATCH /ads/{ad_id}.

Machine identity: api_key is returned exactly once in the 201 Created response to the first bid. Subsequent enrichment updates use X-Api-Key: {key} on PATCH /ads/{ad_id}.

Content negotiation on advertiser profile URLs: Accept: text/html returns the HTML profile page; Accept: application/json returns the full advertiser object (enrichment fields, bid log, rank history). One URL, two representations.

Consequences

Positive
  • Standard HTTP: any HTTP client in any language can consume it (curl, Python requests, Go net/http)
  • MPP is an open standard (co-authored by Stripe and Tempo), not a proprietary protocol
  • Content negotiation enables clean URL design: one canonical URL serves both audiences
  • api_key design is simple: no OAuth, no refresh tokens, no scopes to manage
  • Foundation for MCP: the MCP server is a thin wrapper over REST calls, not a separate data layer
Negative
  • api_key returned only once: a lost key means the ad keeps running but cannot be topped up without a new placement
  • Machines must implement MPP client-side unless they use the MCP server (see ADR-006)
  • Content negotiation is a server concern; the static prototype cannot implement it — it is a backend design decision formalised here for production

Alternatives Considered

OptionWhy not chosen
gRPCBinary protocol; less accessible to curl / HTTP clients; transcoding to REST would be needed anyway; overkill for four endpoints
GraphQLMore expressive query surface than needed for a simple catalog; adds schema complexity without benefit; MPP payment headers map naturally to HTTP, not GraphQL operations
Separate URLs for HTML and JSONDuplicates canonical state; breaks Open Graph (og:url must match the page URL); agents following HTML URLs would need to know a separate API URL