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
·
Month 1. The plan grants 1,000 credits.
Set
unusedPlanCreditson a credits plan toroll_over. Expiring stays the default.maxPlanCreditscaps the total a subscription holds after a reset, including the credits that reset grants. Credits above it expire and firecredits.expired.Existing subscriptions apply the change from their next reset. Purchased credits never expire either way.
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
·
·
·
The plan grants 3,000 credits for the month.
planCreditLimittakes a number of credits and an interval,dayorweek. Windows run on the subscription's billing cycle.Past the limit, usage returns
402 plan_credit_limit_reacheduntil 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.
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
·
A customer needs 750 credits to finish a job.
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.
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
·
·
00:00. A batch job starts. It needs 1,800 credits; the balance is 1,000.
When the balance reaches
threshold, Commet buysquantitycredits with the saved payment method, at mostmaxPerPeriodtimes per period. Each purchase firescredits.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.
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
Day 0. Someone signs up and picks Pro.
Trials now live on the plan price, from 1 to 730 days. Set
trialRequiresPaymentMethod: falseand 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_methodwhen the group has none. Each customer gets one trial per plan.Offers are now discounts only. Creating an Offer with a
free_trialphase returns 400; set the trial on the price instead.
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 })
A customer is created with the plan group.
Mark a free plan as the group's default. Create customers with
planGroupIdand 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:
| Change | When it takes effect |
|---|---|
| Free to a paid plan | Now, 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 interval | Now, prorated |
| To a shorter interval, or a lower plan on the same interval | At the end of the period, and it can be revoked |
| Paid to free | At the end of the period, and it can be revoked |
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
A partner joins your Dub program and gets ANA10, 10% off forever.
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
·
A new customer signs up with WELCOME20.
firstPurchaseOnlylimits a code to customers with no paid invoice above zero and no paid subscription.changePlanandpreviewChangeacceptpromoCodefor 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.
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 })
A customer in Mexico pays Pro at $20 a month with an OXXO voucher.
Customers pay 1 to 60 periods upfront, at creation with
prepaidPeriodsor later through a hosted link. Covered renewals charge nothing for the plan; usage, seats and add-ons still bill.cancelAtPeriodEnd: trueends the subscription after the last paid period. Unused periods are not refunded.Plans and subscriptions can also bill every six months with the
semiannualinterval.
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_urlopens 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/v2with 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.
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.
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.