Pricing DSL
Type-safe pricing expressions with mul, add, tag, inputTokens, and more
Scrawn includes a type-safe DSL for expressing pricing logic. Use the builder functions (mul, add, tag, etc.) to build expressions, then wrap them with biller.expr() wherever a debit value is accepted.
Builder functions
These functions create expression nodes. Combine them with biller.tag(), biller.expr(), and numeric values.
biller.tag("NAME") Events & AI
Reference a price tag managed via dashboard on the backend.
biller.tag("PREMIUM_CALL");
biller.tag("EXTRA_FEE");Prop
Type
mul(...args) Events & AI
Multiply two or more values.
import { mul } from "@scrawn/core";
mul(biller.tag("PER_TOKEN"), 100);
mul(biller.tag("BASE_RATE"), 2, biller.tag("MULTIPLIER"));Prop
Type
add(...args) Events & AI
Add two or more values.
import { add } from "@scrawn/core";
add(biller.tag("PREMIUM_CALL"), biller.tag("EXTRA_FEE"));
add(biller.tag("BASE"), 250, biller.expr(100));Prop
Type
sub(...args) Events & AI
Subtract values.
import { sub } from "@scrawn/core";
sub(biller.tag("GROSS"), biller.tag("DISCOUNT"));Prop
Type
div(...args) Events & AI
Integer division.
import { div } from "@scrawn/core";
div(biller.tag("TOTAL"), 2);Prop
Type
inputTokens() AI only
outputTokens() AI only
Token count placeholders that resolve to the actual token count of the current AI call. Only valid inside AITokenUsagePayload objects.
No parameters. Returns a PriceExpr that resolves to the token count at billing time.
import { mul, inputTokens, outputTokens } from "@scrawn/core";
mul(biller.tag("GPT_INPUT_RATE"), inputTokens());
mul(biller.tag("GPT_OUTPUT_RATE"), outputTokens());Nested expressions
You can nest biller.expr() references inside compound expressions - useful when one persisted expression references another, or when mixing persisted expressions with inline pricing:
import { mul } from "@scrawn/core";
// biller.expr("COMPLEX_FEE") returns an ExprRef that serializes
// to "expr(COMPLEX_FEE)" inside the larger expression string.
biller.expr(mul(biller.tag("EXTRA_FEE"), biller.expr("COMPLEX_FEE")));
// → serialized as: mul(tag(EXTRA_FEE), expr(COMPLEX_FEE))The backend resolves expr(COMPLEX_FEE) from its persisted expression store at evaluation time.
Usage in Events
Pass a pricing expression where a debit value is accepted.
Tag reference:
import { biller } from "./scrawn/biller";
await biller.basicUsageEventConsumer({
userId: "user-123",
debit: biller.tag("PREMIUM_CALL"),
});Compound expression - wrap with biller.expr():
import { biller } from "./scrawn/biller";
import { mul } from "@scrawn/core";
await biller.basicUsageEventConsumer({
userId: "user-123",
debit: biller.expr(mul(biller.tag("PER_CALL"), 3)),
});For AI token billing, use inputTokens() and outputTokens() as placeholders:
async function* generateUsage() {
yield {
userId: "user-123",
model: "gpt-4",
inputTokens: 100,
outputTokens: 50,
inputDebit: biller.expr(mul(biller.tag("GPT_INPUT_RATE"), inputTokens())),
outputDebit: biller.expr(mul(biller.tag("GPT_OUTPUT_RATE"), outputTokens())),
};
}
await biller.aiTokenStreamConsumer(generateUsage());Persisted Expressions
Store complex expressions on the backend and reference them by name:
await biller.basicUsageEventConsumer({
userId: "user-123",
debit: biller.expr("MY_PRICING_EXPR"),
});Error Handling
Invalid expressions (wrong types, missing operands, etc.) throw PricingExpressionError at runtime. The TypeScript types catch most issues at compile time.