DRAG ME PLEASE...
// USAGE METERINGnot me!!

BILL
YOUR
SELF-
ROLLED
ABOMINATION.

IN ONE-ISH LINE.
billing.ts
// No more usage-based billing nightmares. // Just fire, forget & get paid. import { scrawn } from "@scrawn/core" const biller = scrawn({ apiKey: process.env.SCRAWN_KEY, baseURL: "http://localhost:8069", }) await biller.basicUsageEventConsumer({ userId: "cus_123", debit: 4500, })
STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///STOP WRITING BILLING LOGIC /// FIRST CLASS INTEGRATIONS /// FIRE AND FORGET /// GET THAT BAG ///
% * + - = $ % * + - = $ % * + - = $ % * + - = $ % * + - = $ % * + - = $
The Missing Aggregator for DodoPayments

Mandatory cringe tagline on a landing page

Dodo gets the cash.
We do the math.

// NO CRON JOBS

Dodo is great at checkout. Too bad you can't make the user pay for each and every request they send.
At least not monetarily.😏

Instead of writing an entire data ingestion pipeline to keep track of payments and sync to Dodo, just fire events to Scrawn. We hook into your existing Dodo account (you keep full control of the money) and aggregate the usage, evaluate your weird pricing logic, and serve the final payment link on a silver platter for you.

Usage AggregationMath EngineStatelessDodo Native

The Orchestration Layer.

Scrawn wraps DodoPayments, meters usage across your stack, and pipes data into your DB.

Web Backend

Node [for now]

AI Backend

Vercel AI SDK

Scrawn Engine

DodoPayments

CHECKOUTINVOICINGTAX

PostgreSQL

Primary Data Store

ClickHouse

Fast Telemetry

/// COMING SOON ///

Everything you need.
Nothing you don't.

Wrap Vercel AI SDK.

Auto-bills tokens on every step finish.
Zero extra code.

LIVE STREAM
generate me some random words
Output
Words will appear here...
Input
8
Output
0
Total Usage
(8 × 3¢) + (0 × 5¢)
= 24¢ + 0¢ = 24¢
events.ts
import * as ai from "ai"
import { mul, inputTokens, outputTokens } from "@scrawn/core"
// Wrap the AI SDK — one line
const aii = biller.ai(ai, {
inputDebit: mul(inputTokens(), 3),
outputDebit: mul(outputTokens(), 5),
});
// Auto-bills every stream
const result = await aii.streamText({
userId: "usr_123",
model: google("gemini-2.5-flash"),
prompt: "generate me some random words",
});
// consumed: 8 input, 0 output — 24¢

The Scrawn Way.

Same endpoint. One is 60 lines of pain. The other is Scrawn.

manual_usage_event.ts
// 1. Manual gRPC setup + proto types import { EventServiceClient } from "./gen/event/v1/event"; import { ChannelCredentials, Metadata } from "@grpc/grpc-js"; // 2. Manual DB client + auth lookup import { db, apiKeys, basicUsageEvents, tags } from "./db/schema"; import { eq } from "drizzle-orm"; import crypto from "node:crypto"; // 3. Init gRPC + DB connections const grpcClient = new EventServiceClient("localhost:8069", ChannelCredentials.createInsecure()); const redis = new Redis(process.env.REDIS_URL); export async function POST(req) { const body = await req.json(); // 4. Auth: hash key, DB lookup, check expiry const apiKeyHash = crypto.createHash("sha256").update(req.headers.get("x-api-key")).digest("hex"); const [key] = await db.select().from(apiKeys).where(eq(apiKeys.key, apiKeyHash)); if (!key || key.revoked) return Response.json({ error: "unauthorized" }, { status: 401 }); // 5. Idempotency: HMAC key + Redis + DB check const idempotencyKey = crypto.createHmac("sha256", env.SECRET).update(`${body.userId}:${body.nonce}`).digest("hex"); const cached = await redis.get(`idempotent:${idempotencyKey}`); if (cached) return Response.json(JSON.parse(cached)); // 6. Resolve pricing: tag lookup or expr eval let debitAmount = body.amount; if (body.tag) { const [tag] = await db.select({ value: tags.value }).from(tags).where(eq(tags.name, body.tag)); debitAmount = tag.value; } else if (body.expr) { debitAmount = await evaluatePricingExpression(body.expr, db); } // 7. Validate debit amount if (debitAmount < 0) return Response.json({ error: "invalid amount" }, { status: 400 }); // 8. Transaction: ensure user + insert event try { await db.transaction(async (txn) => { await upsertUser(body.userId, txn); const [event] = await txn.insert(basicUsageEvents).values({ eventId: crypto.randomUUID(), idempotencyKey, userId: body.userId, apiKeyId: key.id, debitAmount, metadata: body.metadata ?? {}, }).returning({ id: basicUsageEvents.id }); }); // 9. Cache idempotency result in Redis await redis.setex(`idempotent:${idempotencyKey}`, 86400, JSON.stringify({ success: true })); return Response.json({ success: true }); } catch (err) { console.error("billing failed", err); Sentry.captureException(err); return Response.json({ error: "billing_failed" }, { status: 500 }); } } // → 60+ lines: proto, gRPC, DB, Redis, auth, idempotency, pricing — all you

Questions
you're too
embarrassed

to ask.

We don't do Stripe. We're DodoPayments native. Think of us as the middleware that makes Dodo actually work for usage billing. We count the usage, resolve prices, and hand you a Dodo checkout URL. They are an MoR, so they handle the tax, invoicing, and PCI compliance.
SCRAWN SCRAWN SCRAWN SCRAWN
SCRAWN SCRAWN SCRAWN SCRAWN
SCRAWN SCRAWN SCRAWN SCRAWN
SCRAWN SCRAWN SCRAWN SCRAWN
SCRAWN SCRAWN SCRAWN SCRAWN
SCRAWN SCRAWN SCRAWN SCRAWN

Stop building billing
Make them pay.

Wrap your endpoints. We handle the metering and the pricing DSL. You handle the features that actually matter.

STUCK?

We reply fast. Usually.

EMAIL US
hello@scrawn.dev