• Pricing
  • Blog
Log inBook a demo
Changelog
November 2, 2026

Credits that roll over and refill, trials without a card, and Dub

Credits that roll over and refill, trials without a card, a default plan to land on, Dub partner codes, and SDK 9.8 in five languages.

D
Decker Urbano·@0xDecker

October was about credits and plans: credits that roll over, pace themselves and refill, trials that start without a card, a default plan for customers to land on, and Dub partner codes. This post also covers what shipped since SDK 9 and never got its own post. Each feature comes with the case it solves, a before and after, and the code to turn it on.

The first four features are for credits plans. Balance plans keep their manual top-ups and metered plans work as before. Everything after them works with any plan.

Keep the credits customers did not use#

When it helps: you sell a plan with 1,000 credits a month and your customers' usage changes from month to month. A customer who uses 400 watches 600 disappear at the reset. Rolling them over keeps the credits they paid for, and a maximum caps how much unused balance you carry.

Unused credits expire

1,000

·

·

Roll over, up to 2,000

1,000

max 2,000

·

1/5

Month 1. The plan grants 1,000 credits.

Without rollover, unused plan credits expire at every reset. With rollover and a maximum of 2,000, a customer who uses 400 of 1,000 starts the next month with 1,600, and only credits above 2,000 expire.
  • Set unusedPlanCredits on a credits plan to roll_over. Expiring stays the default.

  • maxPlanCredits caps the total a subscription holds after a reset, including the credits that reset grants. Credits above it expire and fire credits.expired.

  • Existing subscriptions apply the change from their next reset. Purchased credits never expire either way.

rollover.ts
await commet.plans.update({
  id: "plan_pro",
  unusedPlanCredits: { type: "roll_over", maxPlanCredits: 2000 },
});

Stop one bad day from spending the month#

When it helps: an agent stuck in a loop, a leaked API key or an overeager script can spend a month of credits in an afternoon. A daily or weekly limit paces the plan's credits so they last the period, and your app gets a clear error instead of an empty balance.

No limit

3,000

·

·

·

200 credits per day

3,000

·

·

·

1/5

The plan grants 3,000 credits for the month.

Without a limit, a runaway agent trying to spend 1,000 credits a day spends a 3,000-credit month in three days. With a limit of 200 credits per day, it spends 200 a day and still has 2,400 left on day 4.
  • planCreditLimit takes a number of credits and an interval, day or week. Windows run on the subscription's billing cycle.

  • Past the limit, usage returns 402 plan_credit_limit_reached until the next window. Usage checks report what was spent, what remains and when the window resets.

  • The limit only counts plan credits. Credits a customer bought keep working after the limit is reached.

spend-limit.ts
await commet.plans.update({
  id: "plan_pro",
  planCreditLimit: { credits: 200, interval: "day" },
});

Sell exactly the credits a customer needs#

When it helps: a customer needs 750 credits to finish a job and your packs come in 500. They either buy twice or pay for credits they did not need. With a credit price on the plan, they buy 750.

Packs of 500 at $10

0

needs 750

·

Any quantity at $0.02

0

needs 750

·

1/2

A customer needs 750 credits to finish a job.

With packs of 500 credits at $10, a customer who needs 750 buys two packs for $20. With a credit price of $0.02, they buy 750 credits for $15.
  • Set a credit price and an optional minimum quantity on the plan. Customers buy any whole quantity from the minimum up.

  • The purchase charges the saved payment method and adds purchased credits, which never expire. Customers can also buy from the customer portal. Credit packs keep working as before.

buy-credits.ts
await commet.subscriptions.purchaseCredits({
  id: "sub_123",
  quantity: 750,
});

Keep long-running jobs from running dry#

When it helps: agents and batch jobs run when nobody is watching. If credits run out at 2 a.m., the job stops and the customer finds out in the morning. Auto top-up buys more when the balance gets low, within a limit per period.

Credits run out

1,000

·

·

·

+500 at 100, up to 3 times

1,000

threshold 100

·

·

1/4

00:00. A batch job starts. It needs 1,800 credits; the balance is 1,000.

Without auto top-up, a job that needs 1,800 credits stops when a 1,000-credit balance runs out. With auto top-up buying 500 credits at a 100-credit threshold, it buys twice and the job finishes.
  • When the balance reaches threshold, Commet buys quantity credits with the saved payment method, at most maxPerPeriod times per period. Each purchase fires credits.purchased.

  • It uses the plan's credit price and minimum. After three failed payments in a row it pauses; saving it enabled again resumes it.

  • You configure it per subscription, and customers can turn it on from the customer portal.

auto-top-up.ts
await commet.subscriptions.updateAutoTopUp({
  id: "sub_123",
  enabled: true,
  threshold: 100,
  quantity: 500,
  maxPerPeriod: 3,
});

const autoTopUp = await commet.subscriptions.getAutoTopUp({ id: "sub_123" });
// autoTopUp.status: "active" | "paused" | "off"

Read consumption models and credit purchases.

Let people try before asking for a card#

When it helps: someone signs up to try your product and the first screen asks for a card. A price can now start its trial without one, so people try first and decide later.

Card required to start

Checkout · add a card to start

·

Trial without a card

Pro · trialing

trial.started · no checkout

1/4

Day 0. Someone signs up and picks Pro.

With a card required, a new signup has no subscription until they add a card. With a trial that needs no card, they start on Pro right away, move to the free default plan when the trial ends, and upgrade when they are ready.
  • Trials now live on the plan price, from 1 to 730 days. Set trialRequiresPaymentMethod: false and the subscription starts trialing with no checkout.

  • A trial that ends without a card moves to the plan group's default plan, or is canceled with trial_ended_without_payment_method when the group has none. Each customer gets one trial per plan.

  • Offers are now discounts only. Creating an Offer with a free_trial phase returns 400; set the trial on the price instead.

trial.ts
await commet.plans.updatePrice({
  id: "plan_pro",
  priceId: "price_pro_monthly",
  trialDays: 14,
  trialRequiresPaymentMethod: false,
});

Move between plans without losing customers#

When it helps: you run a free plan and paid plans in one plan group. New customers should land on Free without a checkout, and a paid customer whose payments stop should drop back to Free instead of losing the account.

Group without a default plan

No subscription until checkout

·

Group with a free default plan

Free · default plan

customers.create({ planGroupId })

1/4

A customer is created with the plan group.

Without a default plan, a customer has no subscription until checkout and is canceled when payment retries run out. With a free default plan, they start on Free, upgrade to Pro, fall back to Free when retries run out, and upgrade again with one call.
  • Mark a free plan as the group's default. Create customers with planGroupId and they start on the group's starting plan: its card-less trial the first time, the free default plan otherwise.

  • A paid plan that ends from the customer portal, a revoked payment authorization or exhausted payment retries moves to the default plan instead of canceling.

  • A change scheduled for the end of the period can be revoked from the API, the customer portal ("Keep current plan") or the dashboard ("Revert plan change").

How a change between plans of the same group takes effect:

ChangeWhen it takes effect
Free to a paid planNow, through checkout, or by starting the price's card-less trial the first time
To a longer interval, or a higher plan on the same intervalNow, prorated
To a shorter interval, or a lower plan on the same intervalAt the end of the period, and it can be revoked
Paid to freeAt the end of the period, and it can be revoked
plans.ts
await commet.customers.create({
  email: "ana@example.com",
  planGroupId: "pg_main",
});

const preview = await commet.subscriptions.previewChange({
  id: "sub_123",
  planId: "plan_max",
});

await commet.subscriptions.changePlan({ id: "sub_123", newPlanId: "plan_max" });

await commet.subscriptions.revokeScheduledPlanChange({ id: "sub_123" });

Read plan groups and upgrades and downgrades.

Run a partner program with Dub#

When it helps: you pay partners a commission for the customers they bring. Until now that meant copying every partner code into your billing and reporting every sale back to Dub by hand.

Partner codes by hand

Create the Offer and code in Commet

·

Dub connected

ANA10 appears as a promo code

managedBy: "dub" · first purchase only

1/4

A partner joins your Dub program and gets ANA10, 10% off forever.

By hand, each partner code has to be created in Commet and every sale and refund reported to Dub. With Dub connected, partner codes appear in Commet on their own, and sales and refunds reach Dub attributed to the partner.
  • Connect Dub from Settings → Integrations with a Dub API key. It needs a Dub workspace on the Business plan.

  • Dub's partner discounts become Offers and partner codes become first-purchase promo codes, marked managedBy: "dub". New codes sync once you add the webhook URL Commet shows you in Dub.

  • Commet sends Dub sales, refunds, cancellations, trial starts and customer changes, and retries what does not get through. Terms are edited in Dub; in Commet you can rename an Offer and turn a code on or off.

Read Dub partner codes.

Promo codes for new and existing customers#

When it helps: a welcome discount should reach new customers only, and an upgrade campaign should reach the customers you already have.

WELCOME20 for anyone

Pays $16.00 instead of $20.00

·

WELCOME20 with firstPurchaseOnly

Pays $16.00 instead of $20.00

·

1/2

A new customer signs up with WELCOME20.

A welcome code without restrictions also discounts customers who already pay. With firstPurchaseOnly, a new customer pays $16 instead of $20 and an existing one is told the code is only for a first purchase.
  • firstPurchaseOnly limits a code to customers with no paid invoice above zero and no paid subscription.

  • changePlan and previewChange accept promoCode for immediate changes, validated with the checkout's rules.

  • Buyers can enter a code in a card trial checkout; the discount starts with the first invoice after the trial. A code whose Offer starts with free cycles saves the card and settles a $0 first invoice.

promo-codes.ts
await commet.promoCodes.create({
  code: "WELCOME20",
  offerId: "offer_welcome",
  firstPurchaseOnly: true,
});

await commet.subscriptions.changePlan({
  id: "sub_123",
  newPlanId: "plan_pro",
  promoCode: "UPGRADE30",
});

Read promo codes.

Sell months in advance#

When it helps: some customers would rather pay several months at once, and cash payments like OXXO cannot renew on their own. Selling the months upfront keeps them on a monthly plan without switching the interval.

One month per voucher

$20.00 voucher · 1 month

·

6 months on one voucher

$120.00 voucher · 6 months

createPrepaidPeriodsCheckout({ periods: 6 })

1/3

A customer in Mexico pays Pro at $20 a month with an OXXO voucher.

With one month per OXXO voucher, the subscription ends after the first month unless the customer adds a card. With six months on one voucher, it stays active for six months and charges nothing for the plan in between.
  • Customers pay 1 to 60 periods upfront, at creation with prepaidPeriods or later through a hosted link. Covered renewals charge nothing for the plan; usage, seats and add-ons still bill.

  • cancelAtPeriodEnd: true ends the subscription after the last paid period. Unused periods are not refunded.

  • Plans and subscriptions can also bill every six months with the semiannual interval.

prepaid.ts
await commet.subscriptions.createPrepaidPeriodsCheckout({
  id: "sub_123",
  periods: 6,
});

Read pay in advance.

More ways to pay#

  • Google Pay and Pix. Turn them on per dLocal connection in Settings → Payments. Google Pay saves the card for renewals; Pix lets buyers in Brazil pay in BRL.

  • OXXO in Mexico. Buyers without a card pay with a cash voucher shown right in the checkout, with the amount, expiry and barcode. The subscription activates when the payment is confirmed.

  • Mercado Pago in Argentina. Buyers authorize once in the checkout and renewals are charged automatically.

  • ID checks before paying. On dLocal checkouts, a mistyped CPF, RUT or DNI shows an inline error instead of a declined payment.

  • Portuguese. The checkout and the customer portal are available in Portuguese, along with English and Spanish.

  • Saved payment methods. Customers keep several payment methods in the customer portal and pick the default. A failed renewal is retried on their other saved methods, and updating a card keeps the previous one as a backup.

For your finance team#

  • One currency for every metric. The dashboard home is customizable, every widget explains its calculation, and "All currencies" adds up every payment in USD at the previous day's rate. An optional "MRR after scheduled churn" widget leaves out subscriptions already set to cancel.

  • Receipts and monthly statements. With Commet as merchant of record, every paid payout has a PDF receipt, and Settings → Monthly statements has a PDF and CSV per month that reconcile your balance with your bank deposits.

  • Payouts in 88 countries. Merchant-of-record payouts can go to a local bank account in 88 countries, up from 15.

  • Emails with your brand. All 22 transactional emails were redesigned. Emails to your customers open with your logo or name.

For developers#

  • SDKs 9.8 in five languages. Node, Python, Go, Java and PHP, and CLI 5.4, cover everything in this post with typed methods.

  • An error page per error code. Every API error's doc_url opens a page for that exact code, with its meaning, the fix and whether to retry.

  • Webhook endpoints that keep failing are paused. When the last three deliveries failed after every retry, the endpoint is paused even under steady traffic and you get one email. Turn it back on from Settings → Webhooks or with isActive: true.

  • MCP v2 only. The first MCP server is retired. Use https://commet.co/mcp/v2 with OAuth or an API key, including restricted keys.

  • Refunds no longer cancel. A refund only emits payment.refunded; cancel the subscription separately if you want to.

Terminal
npm install @commet/node@9.8.0
pip install commet-sdk==9.8.0
go get github.com/commet-labs/commet-go/v9@v9.8.0
composer require commet/commet-php:9.8.0
# Java (Gradle): implementation("co.commet:commet-java:9.8.0")

Also since SDK 9#

  • Plan grants. Give a customer the features of a higher plan in the same group for some cycles, until a date or until revoked, without changing their price or invoices. Useful to let a Pro customer try Enterprise before an upgrade call.

  • Customer credits. Grant a money credit, for example after an outage, and it comes off the next invoices before tax.

  • Offers on existing subscriptions. Apply an Offer to a pending checkout without changing its link, or to an active subscription with the discount starting next cycle, for example to keep a customer who asked to cancel.

  • GBP prices. Set pound prices on a plan and UK buyers pay in GBP.

  • Several provider accounts. Connect more than one Stripe or dLocal account and route countries to each.

  • Card promotions. On Enterprise, link an Offer to card BINs so the discount applies only to a partner bank's cards.

since-sdk-9.ts
await commet.customers.createPlanGrant({
  id: "cus_123",
  subscriptionId: "sub_123",
  planId: "plan_enterprise",
  reason: "Enterprise pilot",
  duration: "cycles",
  durationCycles: 1,
});

await commet.customers.createCredit({
  id: "cus_123",
  amount: 2500,
  currency: "usd",
  reason: "Service recovery",
});

await commet.subscriptions.applyOffer({
  id: "sub_123",
  offerId: "offer_retention",
});

Read plan grants, customer credits and the SDK reference.

Subscription billing built for developers and agents

Get startedBook a demo

Accept global payments, handle usage-based pricing, subscriptions, and seats — without building your own infrastructure.

Frameworks

  • Next.js
  • Remix
  • Nuxt
  • SvelteKit
  • Astro
  • Express
  • Hono
  • Django
  • FastAPI

Resources

  • Documentation
  • Guides
  • Blog
  • Templates
  • Changelog
  • Glossary
  • Billing for AI models

Company

  • Solutions
  • About
  • Open Source

Compare

  • Stripe Billing
  • Orb
  • Chargebee
  • Lago
  • Autumn
  • Yuno

AI

  • Commet Agents
  • MCP Server
  • Skills
  • Skills on GitHub
  • Claude Code
  • Codex
  • Cursor
Follow us
© 2026 Commet Co.TermsPrivacy PolicyCookie Policy