Free trials
A trial lets a customer use a plan before you charge them. You set a default trial on the price, override it per subscription when you need to, and tmbilling converts, invoices or cancels the subscription when the trial ends.
Before you begin
You need a test API key and your base URL, set up as in the Quickstart.
export TMBILLING_URL=https://your-api-host
export TMBILLING_API_KEY=sk_test_…
import { Tmbilling } from "@two-minute-billing/sdk";
const tmb = new Tmbilling({
apiKey: process.env.TMBILLING_API_KEY!,
baseUrl: process.env.TMBILLING_URL,
});
// The client does not wrap the price trial fields or the trial-end call yet,
// so those steps call the API directly with this helper.
async function post<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(`${process.env.TMBILLING_URL}${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TMBILLING_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return (await res.json()) as T;
}
Give a plan a default trial
Set the trial on the price. Every subscription to that price starts in trial unless you say
otherwise. trial_end_action decides what happens when the trial ends:
activate_subscription (the default) converts it to a paid subscription, and
cancel_subscription ends it.
const biz = await tmb.businesses.create({ name: "Fernhill Analytics", default_currency: "USD" });
const product = await tmb.products.create(biz.id, { name: "Pro" });
const price = await post<{ id: string }>(`/v1/businesses/${biz.id}/prices`, {
product: product.id,
name: "Pro monthly",
model: "flat",
currency: "USD",
amount_minor: 2900,
interval: "month",
trial_period: 14,
trial_period_unit: "day",
trial_end_action: "activate_subscription",
});
curl -X POST $TMBILLING_URL/v1/businesses \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "name": "Fernhill Analytics", "default_currency": "USD" }'
export BIZ=biz_…
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/products \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "name": "Pro" }'
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/prices \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"product": "pro",
"name": "Pro monthly",
"model": "flat",
"currency": "USD",
"amount_minor": 2900,
"interval": "month",
"trial_period": 14,
"trial_period_unit": "day",
"trial_end_action": "activate_subscription"
}'
{
"object": "price",
"id": "pro-monthly",
"product": "pro",
"name": "Pro monthly",
"model": "flat",
"currency": "USD",
"amount_minor": 2900,
"interval": "month",
"trial_period": 14,
"trial_period_unit": "day",
"trial_end_action": "activate_subscription"
}
Start a trial without a card
Create the customer and subscribe them. You do not need a payment method to start a trial: nothing is charged until it ends.
const customer = await tmb.customers.create(biz.id, {
email: "[email protected]",
first_name: "Maya",
last_name: "Castillo",
currency: "USD",
});
const trial = await tmb.subscriptions.create(biz.id, customer.id, {
items: [{ price: price.id }],
});
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/customers \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"first_name": "Maya",
"last_name": "Castillo",
"currency": "USD"
}'
export CUS=cus_…
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/subscriptions \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "items": [{ "price": "pro-monthly" }] }'
export SUB=sub_…
{
"object": "subscription",
"id": "sub_XXXXXXXXXXXXXXXXXXXXXX01",
"status": "in_trial",
"customer": "cus_XXXXXXXXXXXXXXXXXXXXXX01",
"trial_start": "2026-10-20T19:08:21.726Z",
"trial_end": "2026-11-03T19:08:21.726Z",
"current_period_start": null,
"current_period_end": null,
"mrr": 0
}
The subscription is in_trial, with no invoice. trial_end is when it converts.
cancel_reason: "no_card", and raises no invoice. Send your customer to
hosted checkout or the portal during the trial.Save the customer's card
In test mode, save a test card. In live mode the card comes from your checkout or portal through your payment gateway.
await tmb.paymentMethods.create(biz.id, customer.id, { token: "4242424242424242" });
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/payment_methods \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "token": "4242424242424242" }'
The trial keeps running. tmbilling charges this card when it ends.
End a trial early
When a customer wants to upgrade now, end the trial. tmbilling activates the subscription, raises the first invoice and collects it, in the same call.
const ended = await post<{ status: string }>(
`/v1/businesses/${biz.id}/subscriptions/${trial.id}/edit_trial_end`,
{ immediately: true },
);
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/subscriptions/$SUB/edit_trial_end \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "immediately": true }'
{
"object": "subscription",
"id": "sub_XXXXXXXXXXXXXXXXXXXXXX01",
"status": "active",
"trial_start": "2026-10-20T19:08:21.726Z",
"trial_end": "2026-10-20T19:08:22.204Z",
"current_period_start": "2026-10-20T19:08:22.204Z",
"current_period_end": "2026-11-20T19:08:22.204Z",
"mrr": 2900
}
The response is the subscription. List the customer's invoices to see the first invoice, paid.
To move the end date instead, send { "trial_end": "<ISO 8601 date-time>" } to the same
endpoint. The new end must be in the future. A later date extends the trial, an earlier one
shortens it.
Override the trial per subscription
The price's trial is a default. Change it when you create the subscription:
| Field | Effect |
|---|---|
trial_days: 30 | A 30-day trial in place of the price's trial. |
trial_days: 0 | No trial: tmbilling invoices and charges now. Needs a payment method when auto-collection is on. |
trial_end | An exact trial end date-time, in place of trial_days. |
| neither | The price's trial_period, if it has one. |
Here a returning customer, who already has a card on file, skips the trial:
const paid = await tmb.subscriptions.create(biz.id, customer.id, {
items: [{ price: price.id }],
trial_days: 0,
});
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/subscriptions \
-H "Authorization: Bearer $TMBILLING_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "items": [{ "price": "pro-monthly" }], "trial_days": 0 }'
{
"object": "subscription",
"id": "sub_XXXXXXXXXXXXXXXXXXXXXX02",
"status": "active",
"customer": "cus_XXXXXXXXXXXXXXXXXXXXXX01",
"trial_start": null,
"trial_end": null,
"current_period_start": "2026-10-20T19:08:22.622Z",
"current_period_end": "2026-11-20T19:08:22.622Z",
"mrr": 2900,
"latest_invoice": {
"id": "inv_XXXXXXXXXXXXXXXXXXXXXX01",
"status": "paid",
"total_minor": 2900
}
}
Know what happens when a trial ends
The billing clock ends the trial at trial_end. What it does depends on the price and on the
customer:
| Situation | Result |
|---|---|
trial_end_action: activate_subscription, card on file | The subscription becomes active. tmbilling raises the first invoice and charges the card. |
trial_end_action: activate_subscription, auto-collection off | The subscription becomes active. tmbilling raises the first invoice and sends it, with your payment terms. |
trial_end_action: activate_subscription, auto-collection on, no payment method | The subscription is cancelled with cancel_reason: "no_card". No invoice. |
trial_end_action: cancel_subscription | The subscription is cancelled with cancel_reason: "trial_ended", card or no card. |
A failed first charge follows your dunning settings, like any other invoice.
Listen for these events:
| Event | When |
|---|---|
subscription_trial_ending | 3 days before trial_end. Remind the customer, or ask for a card. |
subscription_activated | The trial converted. |
invoice_generated, invoice_paid | The first invoice was raised and paid. |
subscription_canceled | The trial ended without converting. Read cancel_reason. |
Test the end of a trial
You cannot wait 14 days in a test. Use Time Travel in the dashboard: open your test business,
start Time Travel, and move the clock past trial_end. tmbilling runs everything the billing
clock would do in that time. See Test with Time Travel.
Time Travel is a dashboard tool. An integration's sk_test_ key cannot move the clock.
Check it worked
GET /v1/businesses/{biz}/subscriptions/{sub}showsstatus: "in_trial"and thetrial_endyou expect.- No invoice exists for the subscription until the trial ends or you end it.
- After the trial, the first invoice is
paid(card on file) orpostedand waiting for payment (auto-collection off).
Common errors
| Status | Message | Fix |
|---|---|---|
| 400 | You can change the trial end only on an in-trial subscription. This subscription is active. | The trial already ended. Change the term instead. |
| 400 | We cannot end the trial. This subscription auto-collects, and the customer has no payment method on file. Add a payment method, or disable auto-collection to send the first invoice instead. | Save a card first. |
| 400 | Provide exactly one of trial_end or immediately | Send one of the two fields, not both. |
| 400 | trial_end must be in the future | Send a later date-time. |
| 400 | Auto-collection is on, but the customer has no usable payment method. Add a card first, or create with auto_collection=off. | You created a subscription with no trial (trial_days: 0) and no card. |
Related
Analytics
One endpoint answers every number on a dashboard. Learn the measures, why a stock is not a flow, which unit a figure is in, and how a partial history says so.
Usage-based billing
Charge for what customers use: create a usage price, record usage with idempotency keys, show the running total, and bill it in arrears with the plan fee.