SparkPay

dev.sparkpayPublished by sparkpay.devOfficial publisherv1.0.0Updated Oct 24, 2026

Stripe billing for indie apps: plans, coupons, subscriptions, customers and revenue reporting.

VerifiedStreamable HTTPWeb executableFinanceBusiness & Commerce

Overview

AI-generated overview

SparkPay is a remote MCP server for Stripe billing on indie apps, letting an assistant inspect plans, coupons, subscriptions and revenue and make human-approved

What you can do
List registered apps and their live plan catalogs, including tiers, prices, trials and usage limits, and diff them against committed plan files. Review subscriptions, customers by email, recent payments, cancellations, referrals and coupons. Report revenue and subscriber totals overall and per app, in USD cents, as a latest-payment run-rate rather than lifetime turnover. Create, bulk-create, delete, bulk-delete, list or hide coupons, and manage coupon invite lists. Onboard a new app by planning and applying a plan catalog, then update plan limits, display metadata, pricing or archive tiers. Run a guided wizard that configures an app's hosted pricing page end to end.
When to use it
Useful when you run several small apps on SparkPay and want an assistant to answer billing questions, review churn and sales, or prepare coupon and pricing changes without opening the dashboard. Write actions are gated behind a human confirmation step, so it suits supervised rather than fully automated workflows.
Requirements
A remote streamable HTTP endpoint at sparkpay.dev; no package, environment variables or headers are declared. Write tools need an elicitation-capable client such as Claude Desktop; clients without elicitation are refused, though dry_run previews work anywhere. The account behind the endpoint must own the apps being queried.
Before you install
Many tools write: coupons can be created, deleted, hidden or scheduled, invites added or revoked, plans repriced, renamed, archived or given new limits, and apps registered. Pricing changes mint real Stripe prices, and coupons affect what customers actually pay. Revenue figures are run-rate, not lifetime turnover, so do not treat them as total earnings. Review each confirmation form carefully, since typed values override the arguments.

Installation

In SourceWeft

  1. Open SparkPay in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/dev.sparkpay%2Fsparkpay/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config.

{
  "mcpServers": {
    "sparkpay": {
      "type": "http",
      "url": "https://sparkpay.dev/api/mcp/mcp"
    }
  }
}

Tools

25

list_apps

List the registered apps (app_id + name). Use this first to discover valid app_id values, then pass one to the other tools to scope results to a single product.

Takes no parameters.

list_subscriptions

List subscriptions/registrations, newest first. Optionally filter by app_id, status, or payment_type. Returns metadata (email, status, plan, amounts). Use get_customer for a single user, or revenue_summary for totals.

Parameterslimitapp_idstatuspayment_typeinclude_samples

get_customer

Look up a customer by email. Returns every subscription/registration for that email across apps (or within one app if app_id is given), with full detail: verification, plan, billing period, usage, refunds. Empty when the email is unknown.

Parametersemailrequiredapp_id

list_recent_payments

List recent paid transactions (excludes free-tier rows), newest first, ordered by payment date. Optionally scope by app_id. Use to review recent sales activity. Amounts are in the transaction currency (cents).

Parameterslimitapp_idinclude_samples

revenue_summary

Revenue and subscriber summary in USD cents (normalized from each transaction currency). Returns latest_payments (the MOST RECENT payment summed per customer: a run-rate, NOT lifetime turnover, because amount_paid is overwritten on renewal), refunds, approximate MRR, active/trialing/paid counts, and lifetime_net (the only cumulative total held, accrued per OWNER across all their apps and net of refunds; null when no accrual record exists). lifetime_net counts only charges whose Stripe webhook reached this app, so a missing renewal event under-counts it silently. A 0 or a low figure means "not recorded here", never "no sales". For turnover, accounts or tax, read Stripe directly: it is authoritative, and this database cannot reconstruct lifetime gross. Optionally scope by app_id; omit for platform-wide totals.

Parametersapp_idinclude_samples

revenue_by_app

Per-app rollup in USD cents (latest_payments/refunded/MRR + paid customer count), largest refund-adjusted latest payments first. latest_payments sums the most recent payment per customer, so it is a run-rate and NOT lifetime turnover. No lifetime figure is available per app (accrual is recorded per owner). Use to compare products at a glance; use revenue_summary for one app in depth.

Parametersinclude_samples

list_referrals

List referral records (who referred whom, reward coupon code, pending/rewarded status), newest first. Optionally scope by app_id and/or status.

Parameterslimitapp_idstatusinclude_samples

list_recent_cancellations

Recently canceled paid subscriptions (excludes free rows), most recent cancellation first, with what each customer was paying. Use for churn review; list_subscriptions orders by signup date instead.

Parameterslimitapp_idinclude_samples

list_coupons

List discount coupons (code, percent off, expiry, max uses, use count, which paid tiers they apply to). Optionally scope by app_id and/or restrict to currently-active ones (not expired, not maxed out). Read-only: cannot create, edit, or delete coupons.

Parameterslimitapp_idactive_only

create_coupon

Create a percentage discount coupon for an app. WRITE, human-gated: a real create requires an elicitation-capable client (e.g. Claude Desktop). The human enters the final code, percent, expiry, visibility, and tier scope in a form; those values OVERRIDE the arguments here, which only pre-fill the form message as a proposal. Clients without elicitation are refused (dry_run still works anywhere). Inserts a `coupons` row and best-effort-mints the matching Stripe coupon (checkout backfills it on first use if Stripe is unreachable). The code is uppercased and must be unique per app. By default the coupon applies to every paid tier. Set dry_run:true first to preview the exact row without writing. Cannot discount a free-only app. Three house rules are enforced and cannot be argued out of: every coupon expires within 6 months (omitting expires_at means the 6-month term, not a code that never expires); a coupon at 90%+ is created invite_only, so it is redeemable only by addresses added with invite_to_coupon; and a 90%+ coupon is pinned to the second-highest paid recurring tier and may never cover the top tier or a one-time (lifetime) plan. Use list_coupons afterwards to confirm, and list_plans to see an app's tier slugs.

Parameterscoderequiredapp_idrequiredlisteddry_runmax_usesexpires_atdescriptionapplies_to_tiersdiscount_percentrequiredconfirm_high_discount

bulk_create_coupon

Create the SAME discount code across many apps in one call: the portfolio-promo shortcut (e.g. a summer sale on every owned app). WRITE, human-gated: a real create requires an elicitation-capable client; the human enters the coupon terms ONCE in a form (overriding the arguments here) and that one approval covers the whole batch. Clients without elicitation are refused (dry_run still works anywhere). Runs each app through the same fully-guarded create as create_coupon. Unlike a single call it does NOT fail the batch when an app already has the code: that app is reported `skipped` and the rest are still created. Returns a per-app outcome matrix plus created/skipped/failed counts. Use dry_run:true first to see which apps would create vs skip. Pass explicit app_ids (from list_apps). There is deliberately no 'all apps' shortcut, so a discount can't hit an app you didn't name.

Parameterscoderequiredlistedapp_idsrequireddry_runmax_usesexpires_atdescriptionapplies_to_tiersdiscount_percentrequiredconfirm_high_discount

delete_coupon

Delete one coupon from an app. WRITE, human-gated: a real delete requires an elicitation-capable client; the human sees the exact coupon (code, percent, redemption count) and must click approve. The model cannot confirm on its own, and confirm_used is only consulted for dry_run previews. Clients without elicitation are refused (dry_run still works anywhere). Removes the Stripe coupon object and then the `coupons` row, exactly like the dashboard's delete, so no orphaned Stripe object is left behind. Deleting is permanent and the code becomes reusable afterwards. Set dry_run:true to see exactly which coupon would go (and its redemption count) without touching anything. Use list_coupons first to confirm the code.

Parameterscoderequiredapp_idrequireddry_runconfirm_used

bulk_delete_coupon

Delete the SAME coupon code across many apps in one call: the inverse of bulk_create_coupon, for retiring a portfolio-wide promo. WRITE, human-gated: a real delete requires an elicitation-capable client; the human sees every app the code would be removed from (with per-app redemption counts) and must click approve, and confirm_used is only consulted for dry_run previews. Clients without elicitation are refused (dry_run still works anywhere). An app that never had the code is reported `skipped` rather than failing the batch, since the desired end state already holds there. Returns a per-app outcome matrix plus deleted/skipped/failed counts. Use dry_run:true first to see exactly which apps would lose the code. Pass explicit app_ids (from list_apps) so a delete can't hit an app you didn't name.

Parameterscoderequiredapp_idsrequireddry_runconfirm_used

set_coupon_listed

Show, hide or SCHEDULE an EXISTING coupon on the public pricing pages by setting its `listed` flag and optional `listed_from` start date. WRITE, human-gated: a real write requires an elicitation-capable client and the human sees the code, the discount, which way it is moving and any start date; clients without elicitation are refused (dry_run works anywhere). These are deliberately the ONLY fields an agent can change on a live coupon: the percent, expiry and tier scope are terms somebody may already have been quoted, while `listed`/`listed_from` are purely a display decision. Together they decide whether the public coupons API returns the code today, which is what every app's promo bar draws from; the discount itself does not move and no Stripe object is touched. `listed_from` in the future STAGES a seasonal coupon so it appears on its own date with no cron: it never blocks anyone holding the code from redeeming it, it only holds the code back from the promo bar. Listing with no `listed_from` clears any existing schedule (lists now). Listing an INVITE-ONLY coupon is refused outright, since that would advertise a near-free code to people who cannot redeem it. A `listed_from` with listed:false is refused as a contradiction. Listing an expired/maxed coupon, or scheduling a start at or after expiry, is allowed but warns. A coupon already in the requested state reports `noop`. Use list_coupons first to see codes and their current visibility.

Parameterscoderequiredapp_idrequiredlistedrequireddry_runlisted_from

list_coupon_invites

List who a coupon was offered to and what they did with it: per-address status (invited / redeemed / revoked), where the invite came from, and the timestamps. Also returns the coupon's own `invite_only` flag -- read that FIRST: when it is false the invite rows are recorded but NOT enforced, and anyone holding the code can still redeem it. Read-only. Use list_coupons for the coupon's terms, invite_to_coupon to add addresses, revoke_coupon_invite to withdraw one.

Parameterscoderequiredapp_idrequired

invite_to_coupon

Add email addresses to a coupon's invite list and, by default, switch the coupon to invite-only so the list is actually enforced. WRITE, human-gated: a real write requires an elicitation-capable client and the human sees every address plus whether the coupon is about to be closed to everyone else; clients without elicitation are refused (dry_run works anywhere). Addresses are lowercased and de-duplicated, malformed ones are reported rather than dropped, and an address already on the list is left exactly as it is -- so re-inviting someone who already redeemed does NOT hand them a second free account, and re-inviting someone who was revoked does NOT un-block them. Set enforce_invite_only:false only to stage a list before gating the code; the result then carries a warning that the coupon is still open. Use dry_run:true first to see exactly who would be added.

Parameterscoderequiredapp_idrequiredemailsrequiredsourcedry_runenforce_invite_only

revoke_coupon_invite

Withdraw one address's right to spend a coupon: the targeted block. WRITE, human-gated: a real write requires an elicitation-capable client and the human sees the address and its current status; clients without elicitation are refused (dry_run works anywhere). Works on an address that was never invited too, writing a revoked tombstone, so blocking someone does not depend on whether they were on the list and survives a later bulk re-invite. This only blocks FUTURE redemptions: if the status was already `redeemed` the discount has been granted, and undoing that means cancelling or refunding the subscription in the dashboard. Prefer this over delete_coupon when one recipient misbehaves -- deleting the code punishes every other invitee. Use list_coupon_invites first to see the current status.

Parameterscoderequiredemailrequiredapp_idrequiredreasondry_run

plan_app_kit

Step 1 of onboarding a new app onto SparkPay. Recons app_name against existing apps and the rest of the portfolio's plan catalogs, then returns automated findings plus the minimum set of questions that genuinely need a human answer (plan prices, billing model, redirect URLs, trial length, push-webhook opt-in). Pass the answers to create_app_kit next.

Parametersapp_namerequired

create_app_kit

Step 2 of onboarding a new app onto SparkPay. Applies answers.plan_catalog by minting real Stripe prices (same helper /api/apps/sync-plans uses) and registers/updates the app, then returns a downloadable integration kit (lib/spark-pay client + plans.json + sync script + INTEGRATION.md, modeled on still-applying's golden pattern) plus exact post_steps for env vars and secrets. Refuses to overwrite an existing app's plans unless answers.confirm_overwrite is true. Real secrets (a push-mode webhook secret) are only ever returned in post_steps, never written into a kit file.

Parametersanswersrequiredapp_namerequired

list_plans

Download an app's live plan catalog: tiers, names, prices, trial, and the machine-readable `limits` that gate features inside the app. This is the source of truth each app repo's `bun run plans:sync` regenerates lib/spark-pay/plans.json from, so diff against this rather than trusting the committed file. Stripe price/product IDs are omitted unless include_stripe_ids is set.

Parametersapp_idrequiredinclude_stripe_ids

update_plan_limits

Change the usage limits on an app's existing tiers (e.g. set ai_generations_daily to 200 on pro). WRITE, human-gated: a real write requires an elicitation-capable client; the human sees the per-tier before -> after diff and must click approve. Clients without elicitation are refused (dry_run still works anywhere). Tier-keyed, so one call can set a new limit key across every tier in a single atomic write. Use -1 for unlimited, matching the catalog convention. This writes ONLY the limits object: it never touches Stripe, prices, or plan names, so it cannot mint a price by accident. Changing prices or adding a tier is a different job: use create_app_kit or the dashboard. Set dry_run to preview the before/after without writing. After this succeeds, run `bun run plans:sync` in the app repo and commit the regenerated plans.json so the code and the catalog agree.

Parametersmodeapp_idrequireddry_runupdatesrequired

update_plan_meta

Set an existing tier's display metadata: its name and the two badge flags the public pricing page styles cards from: is_elite (gold/carbon BEST VALUE card with the rainbow title and app icon) and recommended (MOST POPULAR). A tier called "Elite" is NOT styled as elite unless is_elite is actually true on the stored plan; the page reads the flag, never the tier slug, so a catalog whose top tier lacks the flag renders as a plain grey card. Use this to repair that. WRITE, human-gated: the human sees the before -> after diff and approves (clients without elicitation pass through, since this touches no money: it never reaches Stripe, never changes an amount, limits or billing model, and is reversible by calling it again). Setting a flag false REMOVES the key rather than storing false, keeping exported plans.json minimal. Convention is at most ONE is_elite and ONE recommended tier per app: this tool does not clear the flag from its siblings, so unset the old holder in a second call. Use list_plans first for tier slugs and current flags, and run `bun run plans:sync` in the app repo afterwards to regenerate plans.json.

Parametersnametierrequiredapp_idrequireddry_runis_eliterecommended

update_plan_pricing

Change what an app's EXISTING tiers charge: the amount, the billing model (one_time vs subscription), or both. WRITE, human-gated at the highest tier: a real write requires an elicitation-capable client, and the human types the tier, billing model and price into a form. Those typed values OVERRIDE the arguments here, which only pre-fill the proposal, so the model cannot move a price on its own. Clients without elicitation are refused (dry_run still works anywhere). Stripe prices are immutable, so this mints a NEW price and repoints the plan; the old price object is left intact and existing subscribers keep the price they signed up on, so this changes what NEW buyers pay. Switching a tier to one_time also clears its monthly/yearly price ids, so checkout cannot fall back to a recurring price the pricing page no longer advertises. Free, contact and payg tiers are refused: converting those is a catalog change, not a reprice. Use list_plans first for tier slugs and current amounts, and run `bun run plans:sync` in the app repo afterwards to regenerate plans.json.

Parametersapp_idrequireddry_runupdatesrequired

archive_plan

Remove a tier from an app's plan catalog. WRITE, human-gated: a real write needs an elicitation-capable client and the human must approve; dry_run previews anywhere. Every other plan tool edits tiers that already exist, so a catalog could grow but never shrink, and collapsing a multi-tier page down to a single offer was a dashboard-only job. Stripe products and prices are deliberately left intact, exactly as update_plan_pricing leaves the old price behind: removing the row stops the plan being offered and never reaches into anyone's existing subscription. Refuses while the tier still has non-cancelled subscribers (matched on its Stripe price ids, since subscriptions carry a price rather than a tier slug), and refuses to remove an app's last remaining tier. Run `bun run plans:sync` in the app repo afterwards to regenerate plans.json.

Parameterstierrequiredapp_idrequireddry_run

setup_pricing_page

Guided, stage-chained wizard that configures an app's hosted pricing page end to end: plan billing model + price (with clickable recommendations), plan name and badge (elite/recommended), optional free tier, a seasonal coupon (SUMMER26-style code suggested from the current season), favicon-derived theme color + card style, and FAQ content. WRITE, human-gated per stage: each call runs AT MOST ONE elicitation form and commits only that stage, then returns next_call for the model to continue (stages: overview -> plans -> coupon -> branding -> content -> finish). The values the HUMAN types in each form OVERRIDE the arguments here - args only pre-fill form defaults (pass price_suggestions from market research and faq_drafts to seed them). Testimonials are NOT configurable here: they are collected from real customers on Sellular and rendered from there. Form stages are refused on clients without elicitation; overview/finish are read-only recon and work anywhere, as does dry_run (echoes the form + would-be writes, writes nothing). A declined stage leaves earlier committed stages applied - each stage is individually approved, and each effect is reversible via the standalone tools. Stripe prices are minted only by the plans stage with human-typed amounts (existing paid tiers only; free tiers never touch Stripe). After plan changes, run `bun run plans:sync` in consumer app repos. Use list_apps first for app_id.

Parameterstierstageapp_idrequireddomaindry_runfaq_draftscoupon_codediscount_percentprice_suggestions

Version history

1
  1. v1.0.0LatestSep 16, 2026

Score

83% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts

    Declare a GitHub repository with a README, or serve resource_documentation in the OAuth protected-resource metadata.

  • LicenseNot applicable

    A hosted service is used under its provider's terms, so no license is expected here. This check is left out of the percentage.