Pricing

HUME offers declarative JSON plans and optional Luau hooks. New here? Pick your scenario in Recipes, then read Plan extensions for field details. Luau: pricing ctx, classify ctx. API: HTTP API.

Catalog vs plan: metrics and features in the tenant catalog define what you bill; each plan sets its own included_units, overage, and groups. Reusing api_requests on Starter and Pro does not sync quotas — Catalog vs plans explains the split.

Why Luau

Most plans work fine with declarative JSON — tiers, groups, classify routes, and SaaS billing blocks need no code. Luau is there when rules outgrow static config: progressive volume discounts, cross-metric formulas, or custom invoice line items.

  • Readable logic. Luau uses a small Lua-family syntax. Product engineers can read and write pricing rules without learning a proprietary rules language or spreadsheet macros.
  • One script, two hooks. The same plan manifest defines the catalog (plan, metric, tier) and optional runtime hooks (pricing, classify) — no separate config files to keep in sync.
  • Sandboxed by design. Tenant scripts run in an isolated VM: no network, filesystem, or require. Strict timeouts (2 ms for classify, 250 ms for pricing) keep usage ingest and invoice generation predictable.
  • Logic stays in billing. Unlike outbound webhooks on every usage event, classify and pricing run inside HUME — no extra HTTP round-trip, no callback infrastructure to operate, no race between your app and the meter.
  • JSON when you can, Luau when you must. Use Plan extensions for shared quotas, JSON classify, and SaaS billing. Add Luau only when built-in math is not enough — pricing examples, classify examples.

Pricing strategies

From highest to lowest abstraction:

1. Pricing packages (recommended)

POST /pricing/packages — one declarative payload creates metrics, features, and a plan atomically. Without pricing_lua, built-in Go logic handles invoicing and JSON classify at ingest. Full field reference: Plan extensions.

For an entire catalog slice in one shot, use POST /pricing/bootstrap. It accepts the same plan shapes as packages, plus a compact syntax that keeps payloads small:

  • price instead of monthly_price
  • metrics as a map (code → {included, overage})
  • features as a string array (["exports", "sso"])
  • metric name / unit inferred from code when omitted (api_requests → unit request)
curl
01 curl -s -X POST https://api.hume.run/pricing/bootstrap \
02 -H 'Content-Type: application/json' \
03 -H "$AUTH_HEADER" \
04 -H 'Idempotency-Key: bootstrap-v1' \
05 -d '{
06 "plans": [
07 {
08 "code": "free",
09 "price": 0,
10 "metrics": { "api_requests": { "included": 1000 } }
11 },
12 {
13 "code": "pro",
14 "price": 5000,
15 "metrics": { "api_requests": { "included": 100000, "overage": 1 } },
16 "features": ["exports"]
17 },
18 {
19 "code": "enterprise",
20 "price": 20000,
21 "metrics": { "api_requests": { "included": 1000000, "overage": 1 } },
22 "features": ["exports", "sso"],
23 "seat_pricing": { "included_seats": 25, "overage_per_seat": 800 }
24 }
25 ]
26 }'

Shared metrics are created once and reused across plans. The verbose array format still works inside bootstrap entries when you need explicit names or tiers.

2. Luau scripts

Pass pricing_lua to POST /pricing/packages (field name is historical). The engine runs Luau (Lua-family syntax) in a restricted sandbox. JSON-only packages skip the script and use built-in tier/proration logic at invoice time. Script errors return 400 with the compiler or runtime message.

Limits

Compileclassify (usage)pricing (invoice)
Max script size64 KiB
Opcode budget512 (whole module)
Wall timeout2 ms250 ms
Stdlibbase, table, string, math
Globals after setupfrozen (Sandbox())
Host APIDSL helpersclassify ctxDSL helpers + pricing ctx

Not available: os, io, debug, coroutine, require, network, filesystem. Runtime errors and timeouts fail the operation (usage rejected or invoice generation fails).

Catalog builders

Define plan price, metered metrics, and entitlement features.

MethodReturnsPurpose
plan(code, name, opts)plan tableRoot object returned by the script
metric(code, name, unit, opts)metric tableBillable meter on the plan
feature(code, name, opts)feature tableEntitlement flag; enabled defaults to true
tier(from, to, price)tier tableVolume step; omit to for open-ended top tier

plan(code, name, opts)

  • opts.price / opts.monthly_price — base fee, integer abstract units
  • opts.metricslist(metric(...), ...)
  • opts.featureslist(feature(...), ...)
  • opts.pricing — optional function(ctx); overrides built-in invoice math
  • opts.classify — optional function(ctx); runs at usage ingest (see below)

Starter plan — included units + flat overage:

starter.luau
01 return plan("starter", "Starter", {
02 price = 0,
03 metrics = list(
04 metric("api_requests", "API Requests", "request", {
05 included = 1000,
06 overage = 2,
07 })
08 ),
09 features = list(
10 feature("exports", "Exports", { enabled = true })
11 ),
12 })

Tiered overage:

pro-tiered.luau
01 return plan("pro", "Pro", {
02 price = 2500,
03 metrics = list(
04 metric("api_requests", "API Requests", "request", {
05 included = 500,
06 tiers = list(
07 tier(501, 2000, 4),
08 tier(2001, 10000, 3),
09 tier(10001, 2)
10 ),
11 })
12 ),
13 features = list(
14 feature("exports", "Exports")
15 ),
16 })

Invoice line builders

Used inside pricing(ctx).

MethodReturnsPurpose
list(...) / lines(...)arrayBuild arrays; skips nil
line(desc, amount, opts)line tableOne invoice row; amount is integer
prorate(amount, ratio)integerRounded proration — pair with ctx.window_ratio
usage(ctx, code) / included(ctx, code)integerRead from ctx.usage / ctx.included
pricing-fn.luau
pricing = function(ctx)
local over = math.max(usage(ctx, "api_requests") - included(ctx, "api_requests"), 0)
return lines(
line("Base fee", prorate(ctx.monthly_price, ctx.window_ratio)),
over > 0 and line("Usage", over * 2, { qty = over, unit = 2, kind = "usage" }) or nil
)
end

pricing(ctx) — invoice generation

Called once per billing period overlapping the invoice window. Returned lines replace built-in fee + usage math. Examples: Luau pricing ctx → Examples. Field reference: Luau pricing ctx. Max 100 lines.

luau-metered.luau
01 return plan("luau_metered", "Luau Metered", {
02 price = 5000,
03 metrics = list(
04 metric("api_requests", "API Requests", "request", { included = 1000 })
05 ),
06 pricing = function(ctx)
07 local over = usage(ctx, "api_requests") - included(ctx, "api_requests")
08 local first = math.min(math.max(over, 0), 1000)
09 local rest = math.max(over - first, 0)
10
11 return lines(
12 line("Base fee", prorate(ctx.monthly_price, ctx.window_ratio)),
13 first > 0 and line("API requests", first * 3, { qty = first, unit = 3 }) or nil,
14 rest > 0 and line("API requests (discount)", rest, { qty = rest, unit = 1 }) or nil
15 )
16 end,
17 })

classify(ctx) — usage ingest

Runs before a usage event is committed when the user's active plan defines classify. Reroute metrics (peak/off-peak, bulk vs standard) or reject over-limit requests with { allow = false, reason = "..." }. Examples: Luau classify ctx → Examples. Field reference: Luau classify ctx.

time-routed.luau
01 return plan("time_routed", "Time Routed", {
02 price = 1000,
03 metrics = list(
04 metric("tokens", "Tokens", "token"),
05 metric("tokens_peak", "Peak Tokens", "token", { included = 1000 }),
06 metric("tokens_offpeak", "Offpeak Tokens", "token", { included = 1000 }),
07 ),
08 classify = function(ctx)
09 if ctx.metric_code ~= "tokens" then
10 return { metric_code = ctx.metric_code }
11 end
12 if ctx.hour >= 9 and ctx.hour < 18 then
13 return { metric_code = "tokens_peak" }
14 end
15 return { metric_code = "tokens_offpeak" }
16 end,
17 })

3. Manual plans

POST /plans binds existing metrics by metric_id. No Luau compiler — use POST /pricing/packages for scripts. Does not create catalog entities for you.

Abstract billing units

All money-like fields are integers in abstract billing units. There is no currency field in the API. Display labels, taxes, discounts, and FX are downstream concerns.

Monthly Tracked Users (MTU)

An MTU is any unique user that triggers at least one metering event or entitlement check in a calendar month. HUME tracks this automatically: the first billing event for a user in a month creates a synthetic monthly_tracked_users usage event with quantity = 1.