Recurring Advertiser Subscriptions
Advertisers may establish a recurring monthly or yearly Stripe subscription that automatically credits ad spend each billing cycle, maintaining catalog rank without manual rebidding.
Context
The base madcatalog model is pay-per-bid: an autonomous agent must actively
call POST /ads/bid (or the MCP place_ad tool) for
each spend increment. Rank then decays relative to competitors who bid more
frequently. Two problems emerge at scale:
- Operational burden — running a polling loop that checks rank
via
GET /ads/{ad_id}/rank-status($1.00 per check) and rebids on every overtake is non-trivial to operate reliably. - Revenue smoothing — per-bid revenue is lumpy. A subscription model gives madcatalog predictable recurring revenue and gives advertisers predictable cost.
The previously considered alternative — gating machine catalog reads (ADR-010 planned behavior) — was rejected in favour of subscriptions because subscriptions align revenue with advertiser commitment rather than punishing information-seeking behaviour.
Decision
Add a subscription revenue rail alongside the existing per-bid and per-poll rails. Implementation:
- REST API —
POST /subscriptions,GET /subscriptions/{id},DELETE /subscriptions/{id}. Authenticated withX-Advertiser-Api-Key. - MCP tools —
create_subscription,get_subscription,cancel_subscription. - Intervals —
monthly($10/mo) oryearly($99/yr). Prices are env-var-configurable (SUBSCRIPTION_MONTHLY_PRICE_USD_CENTS,SUBSCRIPTION_YEARLY_PRICE_USD_CENTS). - Spend credit — each successful Stripe invoice credits
the billed amount to the advertiser’s
total_spend_usd_centsand, if atarget_ad_idis specified, to that ad’scumulative_spend_usd_cents— boosting its catalog rank automatically. - Payment rail — Stripe subscriptions only. Tempo crypto payments are not supported for recurring billing at this time.
- Cancellation —
DELETE /subscriptions/{id}setscancel_at_period_end=true; the subscription remains active untilcurrent_period_endand one final invoice credit fires.
Consequences
Positive
- Predictable MRR / ARR alongside lumpy per-bid revenue.
- Advertisers can set a subscription with a
target_ad_idand stop operating a polling loop entirely — rank climbs passively. - Telemetry captures all three revenue rails independently in Grafana
(
subscription.payment.amount_cents,subscription.created,subscription.canceled). - Fiat vs crypto breakdown is now first-class in the overview dashboard for all revenue sources, including subscriptions (always Stripe/fiat).
Neutral / Trade-offs
- Subscriptions require a
madcatalog-subscriptionsDynamoDB table with two GSIs (advertiser_slug-index,stripe_subscription_id-index), adding operational surface. - The Stripe webhook handler must now process five event types instead of three, increasing complexity; each new event type is guarded by independent try/catch to avoid cascading failures.
- Spend credited from subscriptions is indistinguishable from bid-based spend
in the ad record itself. A future audit field
(
subscription_spend_usd_cents) could be added without breaking existing rank logic.