What it does
Cloud bills grow in steps nobody notices: a bigger database instance, a log retention setting, a new region. Each bill looks reasonable on its own, and revenue grows too, so the only number that tells you whether the business is getting cheaper or dearer to run is the ratio, and nobody keeps it.
On the 3rd of each month the agent adds up last month's hosting from whichever providers you use, counts paying customers in Stripe as of month end, and divides. It sets the result against MRR for an infrastructure margin and against last month's card, and when the cost per customer jumps it names the provider and service behind it.
How it works
1Total last month's AWS bill
Run
aws ce get-cost-and-usage --time-period Start=<first of last month>,End=<first of this month> --granularity MONTHLY --metrics UnblendedCost --group-by Type=DIMENSION,Key=RECORD_TYPE(the end date is exclusive). Add every record type exceptCreditandRefund, and list credits on their own line: startup credits hide the real cost until they run out.2Add the other providers, whichever you use
Vercel:
GET /v1/billing/charges?teamId=<team>&from=<start>&to=<end>streams FOCUS rows; sumBilledCostand skipChargeCategoryCredit. Cloudflare:GET /accounts/{account_id}/billing/historyand sumamountfor items whoseoccurred_atfalls in the month. Supabase's Management API has no invoice endpoint, so use the monthly amount set in the skill's settings or the invoice total read from the Billing page.3Count paying customers at month end
List
GET /v1/subscriptions?status=activeand keep subscriptions whose monthly amount after discounts is above zero. Rewind to the last day of the month: drop onescreatedafter it, and add backstatus=canceledsubscriptions whosecanceled_atis after it. Count distinctcustomervalues and sum the normalized monthly amounts for MRR.- 4
4Divide and compare with last month
Cost per paying customer = total infrastructure / paying customers. Infrastructure margin = (MRR - infrastructure) / MRR; payment fees and support are not in it, and the card says so. Read last month's ratio and margin from
GET https://briefkin.dev/v1/cards/insight-stripe-infra-cost-per-customer. Alert when cost per customer rose 15% or more, or margin fell 2 points or more. 5Name the service behind a jump
On an alert, rerun Cost Explorer for both months grouped by
SERVICE, and Vercel charges byServiceName, and list the 3 largest increases in dollars next to the change in customers, so "cost grew 42%, customers 3%" points at a line item.Post it to the board
With
BRIEFKIN_TOKENset, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
Install and run it
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
Install the skill
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shell
curl -fsSL https://briefkin.dev/skills/stripe-infra-cost-per-customer/install | shRun it
Ask Claude Code:
prompt
Run the stripe-infra-cost-per-customer skillOr paste the prompt instead of installing:
prompt
Work out what my infrastructure cost per paying customer was last month. Add up last month's bills from whichever of these I use: AWS (Cost Explorer, UnblendedCost, excluding credits and refunds, credits listed separately), Vercel (billing charges, BilledCost), Cloudflare (account billing history) and Supabase (the plan amount I give you). From Stripe, count customers with an active, non-zero subscription as of the last day of last month, and their MRR. Give me cost per paying customer, infrastructure margin ((MRR minus infrastructure) divided by MRR), spend per provider for last month and the month before, and the change since last month. If cost per customer rose 15% or more, or margin fell 2 points or more, list the 3 services that grew most. End with the one cost to look at first.Put it on a schedule
3rd of the month at 9:00, from cron on any machine where Claude Code is signed in:
crontab
0 9 3 * * claude -p "Run the stripe-infra-cost-per-customer skill"Add it with
crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.Send it to your board
Connect Briefkin once with
curl -fsSL https://briefkin.dev/install | shand every run lands on your board as a stat tile. How setup works
View the SKILL.mdRaw file
SKILL.md---
name: stripe-infra-cost-per-customer
description: "On the 3rd of each month: last month's AWS, Vercel, Cloudflare and Supabase bills divided by paying Stripe customers, and what that did to margin. Use when the user asks for this, or when it runs on a schedule."
---
# Infrastructure cost per paying customer: cloud bills and Stripe
On the 3rd of each month: last month's AWS, Vercel, Cloudflare and Supabase bills divided by paying Stripe customers, and what that did to margin.
Cloud bills grow in steps nobody notices: a bigger database instance, a log retention setting, a new region. Each bill looks reasonable on its own, and revenue grows too, so the only number that tells you whether the business is getting cheaper or dearer to run is the ratio, and nobody keeps it.
On the 3rd of each month the agent adds up last month's hosting from whichever providers you use, counts paying customers in Stripe as of month end, and divides. It sets the result against MRR for an infrastructure margin and against last month's card, and when the cost per customer jumps it names the provider and service behind it.
## Two ways to get the data
Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.
## Route A: API or MCP server
### What you need
- Read-only billing access for each provider you use: an AWS role with `ce:GetCostAndUsage`, a Vercel token on a team role that can read billing (Owner, Billing or Enterprise Viewer, among others), a Cloudflare token with billing read.
- A Stripe restricted key with Read on Subscriptions, and Supabase's monthly amount in the settings if you host there.
If something is missing, say what and stop; do not invent numbers.
### Steps
1. **Total last month's AWS bill** (AWS): Run `aws ce get-cost-and-usage --time-period Start=<first of last month>,End=<first of this month> --granularity MONTHLY --metrics UnblendedCost --group-by Type=DIMENSION,Key=RECORD_TYPE` (the end date is exclusive). Add every record type except `Credit` and `Refund`, and list credits on their own line: startup credits hide the real cost until they run out.
2. **Add the other providers, whichever you use** (Vercel): Vercel: `GET /v1/billing/charges?teamId=<team>&from=<start>&to=<end>` streams FOCUS rows; sum `BilledCost` and skip `ChargeCategory` `Credit`. Cloudflare: `GET /accounts/{account_id}/billing/history` and sum `amount` for items whose `occurred_at` falls in the month. Supabase's Management API has no invoice endpoint, so use the monthly amount set in the skill's settings or the invoice total read from the Billing page.
3. **Count paying customers at month end** (Stripe): List `GET /v1/subscriptions?status=active` and keep subscriptions whose monthly amount after discounts is above zero. Rewind to the last day of the month: drop ones `created` after it, and add back `status=canceled` subscriptions whose `canceled_at` is after it. Count distinct `customer` values and sum the normalized monthly amounts for MRR.
4. **Divide and compare with last month**: Cost per paying customer = total infrastructure / paying customers. Infrastructure margin = (MRR - infrastructure) / MRR; payment fees and support are not in it, and the card says so. Read last month's ratio and margin from `GET https://briefkin.dev/v1/cards/insight-stripe-infra-cost-per-customer`. Alert when cost per customer rose 15% or more, or margin fell 2 points or more.
5. **Name the service behind a jump** (AWS): On an alert, rerun Cost Explorer for both months grouped by `SERVICE`, and Vercel charges by `ServiceName`, and list the 3 largest increases in dollars next to the change in customers, so "cost grew 42%, customers 3%" points at a line item.
## Route B: the browser
### What you need
- Signed in to the AWS console with billing access, Vercel as a team member who can see billing, Cloudflare and Supabase with billing access, and Stripe with a role that can view subscriptions.
If something is missing, say what and stop; do not invent numbers.
### Steps
Start at https://console.aws.amazon.com/billing/home#/bills. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.
1. **Read the AWS bill**: On Bills, choose last month in the billing period picker and read the total. Note any Credits line separately and add it back, so a startup credit does not hide the real cost; the Charges by service list shows which services grew.
2. **Read Vercel, Cloudflare and Supabase**: Vercel: team Settings > Billing, read last month's invoice. Cloudflare: Manage Account > Billing > Billing History, add last month's charges. Supabase: the organization's Billing page, Invoices, last month's total. Skip any you do not use.
3. **Count paying customers in Stripe**: Open `https://dashboard.stripe.com/subscriptions`, filter Status `Active` and read the count, leaving out $0 and fully discounted plans. Billing > Overview shows MRR on the same screen; read it for the margin.
4. **Divide and compare**: Cost per paying customer is total infrastructure divided by that count; infrastructure margin is (MRR - infrastructure) / MRR. Compare with the same numbers for the month before, and flag a rise of 15% or more or a margin drop of 2 points or more.
Note: Stripe's subscription list counts customers today, not at month end, so run this early in the month. The dashboard MRR figure can differ slightly from a hand count because of how Stripe treats discounts and tax. Vercel and Cloudflare invoices may straddle the month by a few days.
## The report
- **Infrastructure cost per paying customer** last month, with the monthly trend.
- **Infrastructure margin** against MRR, this month and last.
- **Spend by provider** for both months, and credits used.
- **Paying customers** at month end and the change.
- On a jump, the 3 services that grew most, and one line on what to look at first, for example "RDS doubled after the instance resize on the 8th: check whether the larger class is still needed".
## Post it to Briefkin
When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.
```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "stripe-infra-cost-per-customer/<date of this run, e.g. 2026-10-16>",
"item_key": "stripe-infra-cost-per-customer",
"project": "acme-web",
"source": "Infrastructure cost per paying customer: cloud bills and Stripe skill",
"kind": "metric",
"title": "Infra cost per customer up 38%, margin down to 88%",
"metric": {
"label": "Infra cost per paying customer",
"value": 0.84,
"prefix": "$",
"period_label": "Last month",
"better": "lower",
"period_start": "<start of the period, ISO 8601>",
"period_end": "<end of the period, ISO 8601>"
},
"glance": [
{
"type": "Grid",
"props": {
"columns": 3
},
"children": [
{
"type": "Stat",
"props": {
"label": "Infra margin",
"value": 88,
"unit": "%",
"delta": -3
}
},
{
"type": "Stat",
"props": {
"label": "Infra spend",
"value": "$1,596",
"delta": 42,
"better": "lower"
}
},
{
"type": "Stat",
"props": {
"label": "Paying customers",
"value": 1900,
"delta": 3
}
}
]
}
],
"widget": [
{
"type": "Table",
"props": {
"columns": [
"Provider",
"Last month",
"Month before",
"Change"
],
"align": [
"left",
"right",
"right",
"right"
],
"rows": [
[
"AWS",
"$1,020",
"$640",
"+59%"
],
[
"Vercel",
"$312",
"$240",
"+30%"
],
[
"Supabase",
"$189",
"$167",
"+13%"
],
[
"Cloudflare",
"$75",
"$75",
"0%"
],
[
"Total",
"$1,596",
"$1,122",
"+42%"
]
]
}
},
{
"type": "Callout",
"props": {
"text": "Most of the rise is AWS: Amazon Relational Database Service went from $210 to $480 and EC2 data transfer added $90. Margin here is MRR ($13,300) minus infrastructure only.",
"tone": "warn"
}
}
],
"size": "md"
}
JSON
```
Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.
Set `period_start` and `period_end` to the window the number covers: the tile names it from them ("Last 7 days", "Today", "This month"). For a count right now (open items, a balance), send `"period_label": ""` so no period shows; for another name, send it as `period_label` (at most 24 characters).
## Run it on a schedule
Suggested: 3rd of the month at 9:00 (`0 9 3 * *`). From cron, with the agent signed in on that machine:
```bash
0 9 3 * * claude -p "Run the stripe-infra-cost-per-customer skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```
Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/stripe-infra-cost-per-customer#schedule
Source: https://briefkin.dev/skills/stripe-infra-cost-per-customer
What your agent needs
Read-only billing access for each provider you use: an AWS role with
ce:GetCostAndUsage, a Vercel token on a team role that can read billing (Owner, Billing or Enterprise Viewer, among others), a Cloudflare token with billing read.A Stripe restricted key with Read on Subscriptions, and Supabase's monthly amount in the settings if you host there.
Connecting AWS
- AWS MCP Server, AWS's managed remote server, at
https://aws-mcp.us-east-1.api.aws/mcp(other Regions follow the same pattern, such asaws-mcp.eu-west-1.api.aws). In Claude Code, runclaude mcp add aws-mcp https://aws-mcp.us-east-1.api.aws/mcp --transport httpand sign in through AWS Sign-in; your IAM user or role needs theAWSMCPSignInOAuthAccessPolicymanaged policy for that step. Cursor takes the URL with?oauth=initializeappended, and Codex usescodex mcp add aws-mcp --url https://aws-mcp.us-east-1.api.aws/mcp?oauth=initialize(setup). - The same server through the MCP Proxy for AWS. With credentials from
aws loginor an SSO profile, run it asuvx mcp-proxy-for-aws-cli@latest https://aws-mcp.us-east-1.api.aws/mcp --read-only. The proxy signs each request with SigV4, and--read-onlyhides the tools that may need write permissions. AWS also publishes an open-source Billing and Cost Management MCP server (uvx awslabs.billing-cost-management-mcp-server@latest) focused on Cost Explorer, budgets, anomalies and Savings Plans. - AWS CLI or SDK.
aws ce get-cost-and-usage --time-period Start=2026-10-01,End=2026-10-16 --granularity DAILY --metrics UnblendedCost --group-by Type=DIMENSION,Key=SERVICEreturns the same data. The skill readsAWS_PROFILEandAWS_REGION; Cost Explorer itself is served fromus-east-1.
Run reports under a role that can only read billing data, for example one allowed ce:GetCostAndUsage, ce:GetCostForecast, ce:GetAnomalies and budgets:ViewBudget, or the AWSBillingReadOnlyAccess managed policy: the MCP server can do no more than that role allows.
Connecting Vercel
- Vercel MCP, Vercel's official remote server at
https://mcp.vercel.com, signed in with OAuth. It lists projects and deployments, reads build and runtime logs, and searches Vercel's docs. - The
vercelCLI.vercel ls <project>lists recent deployments,vercel inspect <deployment-url> --logsprints the build log, andvercel logs <deployment-url>shows runtime logs. Pass--token $VERCEL_TOKENand--scope <team>in scheduled runs. - REST API with an access token created under Account Settings > Tokens and scoped to the team, stored in
VERCEL_TOKEN, withVERCEL_TEAM_IDsent asteamId.
Vercel tokens can be limited to one team and given an expiry but can't be made read-only, so use a short-lived token for reports and keep deploy and env var tasks out of the same skill.
Connecting Cloudflare
- Cloudflare API MCP server, Cloudflare's official remote server at
https://mcp.cloudflare.com/mcp. In Claude Code runclaude mcp add --transport http cloudflare https://mcp.cloudflare.com/mcp, in Codexcodex mcp add cloudflare --url https://mcp.cloudflare.com/mcp, and in Cursor add the URL tomcp.json; you then authorize through Cloudflare and choose the permissions to grant. It reaches the whole API through two tools,searchto find endpoints andexecuteto call them (GraphQL analytics queries included), plus a docs tool (docs). The older per-product servers, such as Observability and GraphQL, are deprecated in its favor. - The same server with an API token, for unattended runs: create a token at dash.cloudflare.com/profile/api-tokens and send it as
Authorization: Bearer. An account token also needs Account Resources: Read so the server can find the account ID. - API and Wrangler. The skill reads
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID(the names Wrangler uses too) and posts GraphQL queries;wrangler tail <worker>streams live logs when you need to watch an error happen.
Create the token with only Account Analytics: Read, Zone Analytics: Read and Workers Scripts: Read on the accounts and zones in the report; with no Edit permissions, execute can't change anything.
Connecting Supabase
- Supabase MCP server, Supabase's official remote server at
https://mcp.supabase.com/mcp, signed in with OAuth. URL options narrow it:project_ref=<ref>limits it to one project,read_only=trueruns every query as a read-only Postgres user, andfeatures=database,debugging,docsloads only those tool groups. In Claude Code:claude mcp add --scope project --transport http supabase "https://mcp.supabase.com/mcp?project_ref=<ref>&read_only=true", then/mcpand Authenticate. In Codex, add the URL under[mcp_servers.supabase]in~/.codex/config.tomland runcodex mcp login supabase; in Cursor, put it undermcpServersin.cursor/mcp.json. Supabase is also in Claude's connector directory (docs). - Unattended runs with a token. For CI or scheduled jobs, create a personal access token, store it in
SUPABASE_ACCESS_TOKEN, and send it asAuthorization: Bearerto the same project-scoped, read-only URL. - Direct Postgres or the CLI. Connect with a read-only Postgres role and the connection string from the project's Connect panel, stored in
DATABASE_URL, or runsupabase link --project-ref <ref>and then thesupabase inspect dbcommands.
Supabase's own advice for unattended routines: scope the server to one project, set read_only=true, limit features, and keep production out of reach unless the report needs it.
Connecting Stripe
- Stripe MCP server, Stripe's official remote server at
https://mcp.stripe.com. Add Stripe from Claude's connector directory, or runclaude mcp add --transport http stripe https://mcp.stripe.com/, and sign in with OAuth; clients without OAuth can send a restricted key as a Bearer token. - The Stripe CLI. Run
stripe loginonce, thenstripe subscriptions list --status active --limit 100orstripe invoices list --status open. The CLI defaults to test mode, so add--livefor real data. - API with a restricted key. Create a restricted key (
rk_live_...) in the Dashboard under Developers > API keys, store it inSTRIPE_API_KEY, and give it Read on the resources the report touches: Subscriptions, Invoices, Charges, Customers, PaymentIntents, Balance and Disputes.
Never hand an agent the secret key (sk_live_...); a restricted key with Read where needed and None everywhere else can't refund, cancel or charge anything.
Questions
Why does the agent wait until the 3rd?
AWS keeps adding late usage to a month for a day or two after it ends, and Vercel and Cloudflare post invoices shortly after the month closes. Running on the 3rd gives near-final bills. The customer count is rewound to the last day of the month so both sides of the ratio cover the same period.
Should free and trial users count?
Not in this ratio: it divides by paying customers because they carry the cost. A large free tier still shows up as a higher cost per paying customer, which is the point. If you want cost per active user as well, add an active-user count from your analytics and the agent posts both.
Do I need Briefkin to use the Infrastructure cost per paying customer: cloud bills and Stripe skill?
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a stat tile, so you can glance at it instead of asking again.
Which agents can run it?
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Can it work without an API or MCP server, from my browser?
Yes. Switch the data source to "Your browser" and the steps change to the AWS, Vercel, Cloudflare, Supabase and Stripe pages themselves: the agent opens console.aws.amazon.com/billing/home#/bills in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.