Examples
Design a credit system
Welcome credit, sellable packs, and a monthly allotment, one product, end to end.
Acme Write sells AI drafting. Customers see credits, not dollars. One credit is one cent of usage. Free users get 200 credits. Packs of 1,000 sell for $9. Pro mints 5,000 credits every month. Welcome and packs land as wallet top-ups (convert to micros); the Pro allotment is a bundle include in named credits.
| Surface | Rule | How it lands |
|---|---|---|
| Aliases | acme/draft, acme/rewrite | Models, then bundles allow |
| 1 credit | $0.01 of metered usage | Your conversion rate |
| Welcome | 200 credits at sign-up | users grant → wallet micros |
| Pack | 1,000 credits after Stripe checkout | users grant → wallet micros |
| Pro | 5,000 credits / month | bundles include on pro |
| Safety | $15 key cap | Key spend cap |
Rate helper
Keep the conversion in one module. Everything else, signup, webhooks, the usage page, imports it.
export const MICROS_PER_CREDIT = 10_000n // $0.01
export const toMicros = (credits: number): string =>
String(BigInt(credits) * MICROS_PER_CREDIT)
export const toCredits = (balanceUnits: string): number =>
Number(BigInt(balanceUnits) / MICROS_PER_CREDIT)MICROS_PER_CREDIT = 10_000 # $0.01
def to_micros(credits: int) -> str:
return str(credits * MICROS_PER_CREDIT)
def to_credits(balance_units: str) -> int:
return int(balance_units) // MICROS_PER_CREDITAliases, bundles, and the Pro allotment
Create the drafting names, allow them on Free and Pro, then add the monthly include on Pro. Skip the router if every caller should name acme/draft or acme/rewrite directly.
Create the aliases
seams models add acme/draft --source anthropic/claude-sonnet-4
seams models add acme/rewrite --source anthropic/claude-sonnet-4await seams.models.add("acme/draft", {
sources: [{ provider: "anthropic", model: "claude-sonnet-4" }],
})
await seams.models.add("acme/rewrite", {
sources: [{ provider: "anthropic", model: "claude-sonnet-4" }],
})from seams import models
seams.models.add(
"acme/draft",
sources=[models.AddModelSource(provider="anthropic", model="claude-sonnet-4")],
)
seams.models.add(
"acme/rewrite",
sources=[models.AddModelSource(provider="anthropic", model="claude-sonnet-4")],
)Create the bundle and allow those names
seams bundles add free --name Free
seams bundles add pro --name Pro --price 29/month
seams bundles allow free acme/draft
seams bundles allow pro acme/draft acme/rewrite
seams bundles include pro monthly-credits \
--name "Monthly credits" --unit credits --amount 5000await seams.bundles.create("free", { displayName: "Free" })
await seams.bundles.create("pro", {
displayName: "Pro",
priceMicros: "29000000",
interval: "month",
})
await seams.bundles.allow("free", { models: ["acme/draft"] })
await seams.bundles.allow("pro", { models: ["acme/draft", "acme/rewrite"] })
await seams.bundles.include("pro", {
slug: "monthly-credits",
displayName: "Monthly credits",
unit: "credits",
amount: "5000",
price: null,
thenOnDemand: false,
recurring: true,
})seams.bundles.create("free", display_name="Free")
seams.bundles.create("pro", display_name="Pro", price_micros="29000000", interval="month")
seams.bundles.allow("free", models=["acme/draft"])
seams.bundles.allow("pro", models=["acme/draft", "acme/rewrite"])
seams.bundles.include(
"pro",
models.IncludeGrantRequest(
slug="monthly-credits",
display_name="Monthly credits",
unit="credits",
amount="5000",
price=None,
then_on_demand=False,
recurring=True,
),
)Optional: a router over those aliases
Create the router after the targets are allowed on at least one bundle. Seams attaches it to every bundle that already allows all of its targets. Callers use the router slug the same way they use an alias.
seams routers add writer --strategy cheapest \
--target acme/draft --target acme/rewrite
# attaches to pro (both targets allowed); not to free (rewrite missing)await seams.routers.add("writer", {
strategy: "cheapest",
targets: [{ model: "acme/draft" }, { model: "acme/rewrite" }],
})
// attaches to pro (both targets allowed); not to free (rewrite missing)seams.routers.add(
"writer",
strategy="cheapest",
targets=[
models.AddRouterTarget(model="acme/draft"),
models.AddRouterTarget(model="acme/rewrite"),
],
)Sign-up: key and welcome credit
await seams.users.create(userId, { email, bundle: "free" })
const key = await seams.keys.mint({
endUserId: userId,
bundle: "free",
name: email,
capMicros: "15000000", // $15 safety rail
})
await seams.grants.issue({
endUsers: [userId],
amountUnits: toMicros(200),
reason: "welcome",
idempotencyKey: `welcome:${userId}`,
})seams.users.create(user_id, email=email, bundle="free")
key = seams.keys.mint(
end_user_id=user_id, bundle="free", name=email, cap_micros="15000000",
)
seams.grants.issue(
end_users=[user_id],
amount_units=to_micros(200),
reason="welcome",
idempotency_key=f"welcome:{user_id}",
)Pack purchase webhook
Use the Stripe event id as idempotencyKey. A retry that grants again is credit you gave away.
const PACKS: Record<string, number> = { price_credits_1000: 1000 }
const session = event.data.object as Stripe.Checkout.Session
const credits = PACKS[session.metadata?.priceId ?? ""]
if (credits === undefined) return
await seams.grants.issue({
endUsers: [session.client_reference_id!],
amountUnits: toMicros(credits),
reason: `pack:${credits}`,
idempotencyKey: event.id,
})PACKS = {"price_credits_1000": 1000}
session = event["data"]["object"]
credits = PACKS.get((session.get("metadata") or {}).get("priceId", ""))
if credits is None:
return
seams.grants.issue(
end_users=[session["client_reference_id"]],
amount_units=to_micros(credits),
reason=f"pack:{credits}",
idempotency_key=event["id"],
)Show credits in your product
Read the balance Seams already keeps. Convert for display. Do not keep a second credit column that can drift. On 402 insufficient_credit, send them to buy a pack or upgrade; spend_limit_exceeded means the key cap hit first.
const user = await seams.users.show(userId)
return { credits: toCredits(user.balance.balanceUnits) }