Count what each of your customers uses (AI tokens, documents, seats, API requests, storage) and read it back per customer for billing, plan limits or a usage page.
Usage metering (included from the Pro plan) counts what each of your customers uses, so you can bill by usage, enforce your own plan limits or show a usage page in your app. You define meters (AI tokens, documents generated, minutes transcribed, seats), record usage from your server, from SQL or from the app, and read it back per customer, per hour, day or month, or export it as CSV for your billing system. API requests and file storage per user can be counted for you.
#Set it up
- Open Studio › Integrations › Usage metering and click Turn on. It starts with a meter for AI tokens and API requests counted per user; change both as you like.
- Add your meters: a key (lowercase, such as
ai_tokens), a name and unit for display, and how it counts. Sum adds every quantity up (tokens, documents, minutes). Highest value keeps the largest value per hour, for things a customer has rather than uses (seats, projects). - Choose who a customer is for signed-in users: the user, their organization (the
org_idclaim, see Organizations), or another claim in the token such asapp_metadata.tenant_id.
#Record usage
From your server with the service key you name the customer. The same idempotencyKey is counted once, so a retry or a webhook delivered twice never counts double. Server-side events may be back-dated up to 35 days with at.
import { createClient } from 'stitchdb-js'
const admin = createClient(process.env.STITCHDB_URL, process.env.STITCHDB_SERVICE_KEY)
const completion = await ai.chat(prompt)
await admin.metering.record('ai_tokens', completion.usage.total_tokens, {
customer: org.id,
idempotencyKey: completion.id,
})
// up to 1,000 events in one request
await admin.metering.recordMany([
{ meter: 'seats', quantity: 12, customer: 'acme' },
{ meter: 'documents', customer: 'acme', idempotencyKey: 'doc-881' },
])-- in a trigger, a database function or a cron job
select _stitch_metering.record('acme', 'documents', 1, 'doc-881');
-- count every new row of a table (security definer: the trigger runs as you, not as the signed-in user)
create function count_document() returns trigger language plpgsql security definer set search_path = public as $$
begin
perform _stitch_metering.record(new.org_id::text, 'documents', 1, new.id::text);
return new;
end $$;
create trigger count_document after insert on documents for each row execute function count_document();curl -X POST 'https://<ref>.p.stitchdb.com/metering/v1/record' \
-H "apikey: $SERVICE_KEY" -H "Authorization: Bearer $SERVICE_KEY" \
-H 'Content-Type: application/json' \
-d '{"meter":"ai_tokens","quantity":1532,"customer":"acme","idempotency_key":"cmpl-123"}'
# {"recorded":1,"duplicates":0}The SQL function can be called by the service role and by security definer functions and triggers you write; signed-in users cannot call it directly. It returns true when the event was counted and false for a duplicate key.
#From the app
Mark a meter Apps may record and a signed-in user can record it, for their own customer only and at the current time. Use this for things the user may report themselves; anything you bill for is better recorded from your server.
await stitchdb.metering.record('exports')#Built-in meters
| Meter | What it counts |
|---|---|
api_requests · API requests | Every database, auth, storage and function request made with a signed-in user’s token, per customer and hour. Requests answered 401 or 403 and requests with only the anon or service key are not counted. Shows up within a few minutes. |
storage_bytes · File storage | The bytes of the files each user owns, measured every hour (highest value). Works when the customer is the user. |
Built-in meters are counted for you and don’t use your plan’s events.
#Read usage
Rows per customer, meter and period: the sum for sum meters, the highest value for highest-value meters. The period is hour, day (default) or month, in UTC; without from the last 30 days.
const { data } = await admin.metering.usage({ customer: 'acme', from: '2026-10-01', to: '2026-11-01', group: 'month' })
// data.rows: [{ customer: 'acme', meter: 'ai_tokens', period: '2026-10-01T00:00:00.000Z', value: 1843200 }, …]
// data.meters: { ai_tokens: { name: 'AI tokens', unit: 'tokens', aggregation: 'sum' }, … }
// the largest customers of a period
const { data: top } = await admin.metering.customers({ from: '2026-10-01', meter: 'ai_tokens', limit: 20 })
// top.customers: [{ customer: 'acme', totals: { ai_tokens: 1843200, api_requests: 51234 } }, …]// with “Signed-in users may read their own usage” turned on
const { data } = await stitchdb.metering.usage({ group: 'month' })
const used = data.rows.find((r) => r.meter === 'ai_tokens')?.value ?? 0
showUsage(used, PLAN_LIMIT)#Export for billing
Every row of a period as CSV (customer,meter,period,value), from Export CSV in Studio or from your server, to feed an invoice run or your billing provider’s usage API. Add format=json on the HTTP endpoint for JSON.
const { data: csv } = await admin.metering.exportCsv({ from: '2026-10-01', to: '2026-11-01', group: 'month' })#Alerts at a threshold
Get told when one of your customers uses a lot: under Alerts set a meter and a threshold for the month, such as 1,000,000 AI tokens. When a customer’s usage this month reaches it, the alert fires once per customer per month (a new month starts over). Thresholds count the month to date in UTC: the sum for sum meters, the highest value for highest-value meters. Usage is checked every minute on your project’s server; back-dated usage is caught within six hours.
- Webhook. Add a project webhook (Project settings › Webhooks) with the event
metering.threshold. It is signed and retried like every project webhook, and counts toward your webhook events. - E-mail. Tick E-mail me on an alert and set the address (it starts as yours). Alerts that fire together arrive in one e-mail; at most 100 alert e-mails a month per project, the rest show in Studio only.
{
"id": "abcdefghijklmnop_1042",
"type": "metering.threshold",
"created_at": "2026-10-29T10:04:00.000Z",
"project_ref": "abcdefghijklmnop",
"record": {
"alert_id": "4f1c09ab2e", "alert_name": "Big AI user",
"meter": "ai_tokens", "meter_name": "AI tokens", "unit": "tokens",
"customer": "acme", "threshold": 1000000, "value": 1000412,
"period": "2026-10", "period_start": "2026-10-01T00:00:00.000Z", "fired_at": "2026-10-29T10:04:00.000Z"
}
}Changing an alert’s meter or threshold makes it a new alert, which may fire again for customers that already crossed the old one. GET /metering/v1/alerts lists what fired.
#Push usage to Stripe
Bill usage through your own Stripe account: StitchDB sends each customer’s usage to Stripe as meter events, and Stripe puts it on their invoices.
- In Stripe, create a meter (Billing › Meters) for each thing you bill, and note its event name. Use Sum for sum meters and Last for highest-value meters. Keep the payload keys
stripe_customer_idandvalue. - In Studio › Integrations › Usage metering › Push to Stripe, paste a Stripe secret key (or a restricted key that may write meter events) and set the Stripe event name next to each meter you want pushed. Only admins of the project can see or change the key; it is stored encrypted and never shown again.
- Tell StitchDB which Stripe customer each of your customers is: in Studio, with
PUT /metering/v1/stripe-customersfrom your server, or withselect _stitch_metering.set_stripe_customer('acme', 'cus_Q1w2e3')from SQL. With Stripe sync on, a Stripe customer whose metadatauser_idis your customer id is found by itself, and a customer id that already is a Stripe id (cus_…) is used as is.
// once, when you create the Stripe customer for an organization
await fetch(`${STITCHDB_URL}/metering/v1/stripe-customers`, {
method: 'PUT',
headers: { apikey: SERVICE_KEY, Authorization: `Bearer ${SERVICE_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ customer: org.id, stripe_customer_id: stripeCustomer.id }),
})Every five minutes your project’s server sends the hours that have ended (ten minutes after the hour, so late API request counts make it): for sum meters what was added since the last push, for highest-value meters the hour’s highest value once. Each event is sent with an identifier Stripe uses to drop duplicates, and what was pushed is recorded in your database, so nothing is ever sent twice, also after a restart or a retry. Usage that arrives later for an hour already pushed (back-dated events) follows as one more event. Stripe takes whole numbers: fractions wait until they add up to one.
- Customers without a Stripe customer id are not pushed; their usage waits (up to 34 days, Stripe’s limit for past events) and goes as soon as you set the id. Studio shows how many there are.
- If Stripe is unreachable or busy, events are retried for up to 23 hours. If Stripe refuses one (no such customer, no meter with that event name), it is listed under Failed with Stripe’s answer; fix the cause and click Retry failed. A wrong or revoked key pauses the push until you change it.
- Removing the key or an event name stops the push; turning it on again does not resend what was pushed before.
#HTTP API
| Endpoint | Who | What |
|---|---|---|
POST /metering/v1/record | service key; signed-in users for “Apps may record” meters | One event {meter, quantity, customer, idempotency_key, at} or {events: […]} (up to 1,000) |
GET /metering/v1/usage | service key; signed-in users when allowed | customer, meter, from, to, group |
GET /metering/v1/customers | service key | Totals per customer, largest first: from, to, meter, search, limit, offset |
GET /metering/v1/export | service key | CSV or JSON: from, to, group, meter, customer, format |
GET /metering/v1/alerts | service key | Alerts that fired, newest first: month (such as 2026-10), customer, limit |
PUT /metering/v1/stripe-customers | service key | Stripe customer ids: {customer, stripe_customer_id} or {customers: […]} (up to 1,000); null removes one |
GET /metering/v1/stripe-customers | service key | The Stripe customer ids you set: customer, limit, offset |
#Limits
| Limit | Free | Pro | Scale | Enterprise |
|---|---|---|---|---|
| Usage metering: meters per project | 0 | 20 | 100 | 500 |
| Usage metering: events recorded per project per month | 0 | 5,000,000 | 50,000,000 | 500,000,000 |
| Usage metering: raw events kept | 7 days | 30 days | 90 days | 365 days |
Each recorded event counts once against the month, whatever its quantity. When the month’s events are used up, recording answers 429 (the SQL function raises an error) until the next month; reading keeps working. Totals per hour are kept 400 days; single events (with their idempotency keys) for the days your plan keeps them.