Start here: the big picture
MeterFlow answers one question for your product: “who used how much of what — and what does that cost them?” Everything in the dashboard exists to serve that question.
user-842 or an email).How the pieces connect
Why are some “Create” buttons greyed out?
Every dialog disables its confirm button until all required fields (marked with *) are filled — e.g. “Create plan” stays disabled until the plan has a Name. If a button looks stuck, check for an empty required field above it.
New organization container
The outermost container — usually your company. Everything else (projects, meters, plans…) lives inside an organization. You got one automatically when you registered.
📍 Dashboard → the “New organization” button (also offered on first login, when you have none yet)
- Name *
Your company or team name. Purely a label — you can change it later.
- Billing email
Where invoices and billing notices for your MeterFlow account would go. Optional; format is validated, but nothing is sent to verify it.
finance@acme.dev. Their two products will become two projects inside it.New project container
One project = one product or environment you want to meter. Every meter, plan, API key, customer balance and usage event belongs to exactly one project — projects never see each other’s data.
📍 Dashboard → the “New project” button in the Projects table header
- Organization *
Which organization the project belongs to. Pre-selected if you already have one active.
- Name *
The product’s name, e.g. “PixelForge AI”. Shown across the dashboard and in the project switcher.
- Description
Free text for your own reference.
Generate API key access
The credential your application uses to talk to MeterFlow. Your backend passes it to the SDK; every usage event, credit operation and subscription call is authenticated — and scoped to one project — by this key.
📍 Open a project → API Keys → the button in the table header
- Key name *
A label so you remember where the key is used — “Production backend”, “Staging”, “CI tests”. Has no technical effect.
- Environment
Live keys start with
mf_live_, Test keys withmf_test_— and they access two fully separate datasets inside the same project. Subscriptions, credits and usage created with a Test key are invisible to Live keys (and vice versa), while your meters and plans are shared, so tests always run against your real billing configuration. Use Test keys for development, staging and CI; Live keys only for real production traffic.
mf_live_a1b2… once, and put it in your server’s environment variables: new MeterFlow({ apiKey: process.env.METERFLOW_KEY }). The dashboard list will forever show only the prefix and the last 4 characters.New meter core concept
A meter is a named counter definition: it declares one thing in your product worth measuring, and how raw events should be turned into a number. Think of it as creating a new column in a usage report before any data exists.
📍 Open a project → Meters → the button in the table header
- Name *
The human-friendly label shown in the dashboard — “Images generated”, “Video render seconds”. For people’s eyes only.
- Event name *
The machine key — and yes, it is stored in the database and matched exactly. When your application reports usage, it sends an event name; MeterFlow looks up the active meter in that project whose
event_nameis an exact, case-sensitive match. If none exists, the event is rejected with a 404 error (“No active meter found for event …”) and nothing is recorded. Each event name is unique within a project. Convention: lowercase dotted, e.g.image.generated. - Aggregation type
How many individual events get combined into one number in usage summaries. See the table below — this is the heart of the meter.
- Aggregation field
Required for every type except Count. It names which numeric field of the event carries the quantity being aggregated — it’s the answer to “sum of what?”. In practice your app sends the number in the event’s
valuefield, and that’s what gets aggregated; the aggregation field documents what that value means (e.g.seconds,tokens,megabytes) so anyone reading the meter knows the unit. For Count it’s irrelevant — counting only needs the event to exist. - Description
Free text for your team.
Aggregation types, by example
Suppose the customer user-842 triggers 4 events this month with values 3, 10, 2, 10. One number is reported per meter — which number depends on the aggregation type:
| Type | Question it answers | Result for 3, 10, 2, 10 | Typical use |
|---|---|---|---|
| Count | How many times did it happen? (values ignored) | 4 | Images generated, API calls, exports |
| Sum | How much in total? | 25 | Seconds rendered, tokens consumed, GB transferred |
| Max | What was the single largest? | 10 | Peak concurrent jobs, largest upload |
| Min | What was the single smallest? | 2 | Rarely billing — mostly diagnostics |
| Unique count | How many distinct values appeared? | 3 (3, 10, 2) | Distinct active days, distinct documents touched |
video.rendered · Aggregation Sum · Aggregation field seconds.Your app finishes rendering a 42-second clip for
user-842 and reports the event video.rendered with value 42. Do that a few times and the customer’s usage page shows one line: Video render seconds — 3 events — 117.
Does the event name have to match what my app sends? What happens on a typo?
Yes — exactly, including case. The meter’s event name is stored in the database and acts as the address label for incoming events. If your app sends video.render but the meter says video.rendered, the API answers 404 “No active meter found for event 'video.render' in this project” and the event is not stored. This is deliberate: a silent typo would leak un-billed usage forever. Create the meter first, then start sending events with the same name.
Can I change the aggregation type later?
No — after creation only the name, description and active flag are editable. The aggregation defines what the stored history means, so changing it retroactively would rewrite the past. If you need different maths, create a new meter with a new event name.
Why is “Aggregation field” optional in the form if it says required?
The form leaves it blank-able because it’s only required for non-Count types — the server validates that rule. If you pick Sum/Max/Min/Unique count and leave it empty, the create is rejected with a clear validation error; with Count you can ignore the field entirely.
New plan core concept
A plan is a pricing tier — “Free”, “Pro $29/month”… It bundles a recurring price with a set of meter limits that say how much of each metered feature is included and what happens beyond that. Customers are attached to a plan through a subscription.
📍 Open a project → Plans → the button in the table header
- Name *
The tier name — “Free”, “Pro”, “Enterprise”. Required; the Create button stays disabled until it’s filled.
- Description
Free text, e.g. the marketing one-liner for the tier.
- Price / Currency / Billing period
The recurring subscription fee: Price is the amount (0 is fine for a free tier), Currency a code like
USD, and Billing period how often it recurs — monthly, yearly, weekly, or one-time. Fixed after creation — editing a plan later cannot change these (create a new plan instead, so existing subscribers’ terms don’t silently change). - Trial days
Free days at the start of each new subscription. With
14, a new subscriber spends 2 weeks in a “trialing” status before the paid period starts.0= no trial. - Public
Whether the plan is visible to your application through the SDK’s plan list (e.g. to render your pricing page). Unticked = internal/hidden — useful for custom enterprise deals or plans still being drafted.
Meter limits — the “+ Add limit” section
Each limit row connects this plan to one meter and answers: how much of that thing is included, and what happens beyond it?
- Meter *
Which measured thing this limit applies to. The dropdown lists the meters you created in this project — nothing more, nothing less. It is not a fixed catalogue: any new product capability just needs a new meter first, and it appears here immediately.
- Included units
The allowance bundled into the plan’s price, per billing period. “Pro includes 500 images a month” →
500. - Overage rate
The price in credits per unit once the customer goes past the included units.
0.5means each extra unit costs half a credit from the customer’s balance. Only meaningful with the Metered limit type. - Limit type
Hard (block) — usage beyond the included units should be refused; the customer hits a wall. Soft (warn) — usage continues, but you’re signalled so you can nudge the customer to upgrade. Metered (charge) — usage continues and each extra unit is automatically charged from the customer’s credit balance at the overage rate. Metered is the “pay-as-you-go” option and the only one that moves money (credits) by itself.
USD · Monthly · 14 trial days · Public ✓Limit 1: meter Images generated, included 500, type Hard → after 500 images, generation is blocked until next month or an upgrade.
Limit 2: meter Video render seconds, included 1 000, overage 0.02, type Metered → past 1 000 seconds, every extra second silently costs 0.02 credits from the customer’s wallet.
Why is “+ Add limit” greyed out?
Because the project has no meters yet — a limit is always a cap on a meter, so with zero meters there is nothing to attach it to. The dialog shows the hint “Create meters first to attach limits to this plan.” Go to Meters → New meter, create at least one, then reopen this dialog.
Why does the Meter dropdown only show a few options? What if my product has something new?
The dropdown is simply your own meters for the currently selected project. In the demo data those happen to be “Upscales processed”, “Video render seconds” and “Images generated” — but that list is yours to grow. It does not restrict what customers can do: customers never pick meters; you define meters for whatever your product does. New feature tomorrow? Create a meter for it and it shows up here.
Why can’t I edit price or limits on an existing plan?
By design: existing subscribers signed up under those terms, so price, currency, billing period and meter limits are frozen at creation. Editing a plan can change its name, description, trial days, visibility and active flag only. To reprice, create a new plan (e.g. “Pro 2026”) and move new customers onto it.
New subscription links customer ⇄ plan
A subscription attaches one of your customers to one plan. From that moment their usage is judged against that plan’s meter limits. In production your backend usually creates this via the SDK the instant someone picks a tier at checkout — the dialog does the same by hand.
📍 Open a project → Subscriptions → the button in the table header
- Customer ID *
Your own identifier for the end customer — any string you choose: a user ID from your database (
user-842), an email, a tenant slug. MeterFlow never validates it against anything; whatever you send is who the subscription belongs to. Use the same ID everywhere (subscriptions, credits, usage) or the pieces won’t connect. - Plan *
Which pricing tier they’re on — the dropdown lists this project’s plans. If the plan has trial days, the subscription starts in “trialing”; otherwise “active”.
user-842 clicks “Upgrade to Pro” in your app → your backend calls subscriptions.create({ customer_external_id: 'user-842', plan_id: … }). Every usage event from user-842 is now checked against Pro’s limits.What if a customer has no subscription — are their events lost?
No. Events are always stored and always show in usage summaries. But without an active subscription there are no plan limits to apply, so nothing is blocked and nothing is charged — the usage is simply recorded. Billing logic switches on the moment a subscription exists.
Grant / Deduct credits the wallet
Credits are a prepaid balance per customer (per project) — think of a wallet or top-up card. You decide what one credit is worth in your pricing. Metered overage draws from this wallet automatically; the two dialogs move credits by hand.
📍 Open a project → Credits → look up a customer → “Grant” / “Deduct”
- Amount *
How many credits to add (Grant) or remove (Deduct). Must be greater than zero — the direction is decided by which button opened the dialog, not by a minus sign.
- Description
An optional memo stored on the ledger entry — “Welcome bonus”, “Refund for outage”, “Support goodwill”. Your future self will thank you.
user-842 gets a Grant of 100 (“Welcome bonus”). They render 2 500 video seconds on a plan with 1 000 included and 0.02 metered overage → 1 500 × 0.02 = 30 credits are deducted automatically. Balance: 70.Record usage event core concept
One usage event = “this customer just did this thing, this much.” In production your application sends these through the SDK automatically, thousands of times a day; the dialog exists so you can inject one by hand for testing and demos.
📍 Open a project → Usage → look up a customer → “Record event”
- Event name *
Must exactly match the event name of an existing, active meter in this project (case-sensitive). That match is how MeterFlow knows which counter this event feeds. No matching meter → the API rejects the event with a 404 and nothing is stored. Check the Meters page for the exact spelling.
- Customer ID *
Which of your customers did it — same free-form ID string used for subscriptions and credits. Consistency is everything:
user-842andUSER-842are two different customers. - Value
The quantity, default
1. Its meaning depends on the meter’s aggregation: for a Count meter the value is ignored (each event counts as one occurrence); for Sum it’s the amount to add (42 seconds, 1 300 tokens); for Max/Min it’s the measurement being compared; for Unique count it’s the thing whose distinct values are counted.
video.rendered → “Video render seconds”. 2. The event is stored permanently (raw — aggregation happens later, at reading time). 3. In the background, MeterFlow checks whether the customer has an active subscription and whether that plan has a limit on this meter. 4. If the limit is Metered with an overage rate, the charge (value × rate) is deducted from the customer’s credit balance and a ledger line is written. 5. The usage summary you see on this page recomputes from the raw events using the meter’s aggregation type.
I recorded an event and got an error — why?
Almost always the event name: it doesn’t exactly match any active meter in this project. The error says so verbatim — “No active meter found for event '…' in this project.” Copy the event name from the Meters page rather than retyping it. Also note meters are per-project: a meter in another project doesn’t count.
If my app retries a request, will the event be double-counted?
Not when using the SDK properly: every write accepts an idempotency key — a unique tag for “this particular action”. If the same key arrives twice (a retry after a network hiccup), MeterFlow recognises it and returns the original result instead of storing a duplicate. The manual dialog doesn’t send one, so pressing Record twice by hand does create two events.
Invite member team access
Adds a teammate to your organization so they can see (or manage) its projects in this dashboard. The person must already have a MeterFlow account — registration comes first, invite second.
📍 Dashboard → Organizations table → row action “Invite member”
- Email *
The email their MeterFlow account is registered under.
- Role
Admin — full management: projects, meters, plans, keys, members. Member — day-to-day operations. Viewer — read-only. Note there is deliberately no “owner” option: ownership can’t be handed out via invite.
dana@acme.dev as Viewer so the finance team can watch usage and balances without being able to change pricing.