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.

SurfaceRuleHow it lands
Aliasesacme/draft, acme/rewriteModels, then bundles allow
1 credit$0.01 of metered usageYour conversion rate
Welcome200 credits at sign-upusers grant → wallet micros
Pack1,000 credits after Stripe checkoutusers grant → wallet micros
Pro5,000 credits / monthbundles include on pro
Safety$15 key capKey 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)

Aliases, 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-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 5000

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)

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}`,
})

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,
})

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.

typescript
const user = await seams.users.show(userId)
return { credits: toCredits(user.balance.balanceUnits) }