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_keydesign 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_keyreturned 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
| Option | Why not chosen |
|---|---|
| gRPC | Binary protocol; less accessible to curl / HTTP clients; transcoding to REST would be needed anyway; overkill for four endpoints |
| GraphQL | More 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 JSON | Duplicates canonical state; breaks Open Graph (og:url must match the page URL); agents following HTML URLs would need to know a separate API URL |