On this page

Usage metering

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

  1. 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.
  2. 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).
  3. Choose who a customer is for signed-in users: the user, their organization (the org_id claim, see Organizations), or another claim in the token such as app_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' },
])

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.

JavaScript
await stitchdb.metering.record('exports')

#Built-in meters

MeterWhat it counts
api_requests · API requestsEvery 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 storageThe 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 } }, …]

#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.

JavaScript
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.
metering.threshold
{
  "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.

  1. 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_id and value.
  2. 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.
  3. Tell StitchDB which Stripe customer each of your customers is: in Studio, with PUT /metering/v1/stripe-customers from your server, or with select _stitch_metering.set_stripe_customer('acme', 'cus_Q1w2e3') from SQL. With Stripe sync on, a Stripe customer whose metadata user_id is your customer id is found by itself, and a customer id that already is a Stripe id (cus_…) is used as is.
JavaScript
// 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

EndpointWhoWhat
POST /metering/v1/recordservice key; signed-in users for “Apps may record” metersOne event {meter, quantity, customer, idempotency_key, at} or {events: […]} (up to 1,000)
GET /metering/v1/usageservice key; signed-in users when allowedcustomer, meter, from, to, group
GET /metering/v1/customersservice keyTotals per customer, largest first: from, to, meter, search, limit, offset
GET /metering/v1/exportservice keyCSV or JSON: from, to, group, meter, customer, format
GET /metering/v1/alertsservice keyAlerts that fired, newest first: month (such as 2026-10), customer, limit
PUT /metering/v1/stripe-customersservice keyStripe customer ids: {customer, stripe_customer_id} or {customers: […]} (up to 1,000); null removes one
GET /metering/v1/stripe-customersservice keyThe Stripe customer ids you set: customer, limit, offset

#Limits

LimitFreeProScaleEnterprise
Usage metering: meters per project020100500
Usage metering: events recorded per project per month05,000,00050,000,000500,000,000
Usage metering: raw events kept7 days30 days90 days365 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.