Recipes

Free trials

Give a plan a free trial, start trials with or without a card, change or end a trial early, and know exactly what happens to the subscription when it ends.

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_…
setup.ts
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",
});
Response
{
  "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 }],
});
Response
{
  "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.

With auto-collection on, a trial that ends with no payment method on file does not convert: tmbilling cancels it with 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" });

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 },
);
Response
{
  "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:

FieldEffect
trial_days: 30A 30-day trial in place of the price's trial.
trial_days: 0No trial: tmbilling invoices and charges now. Needs a payment method when auto-collection is on.
trial_endAn exact trial end date-time, in place of trial_days.
neitherThe 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,
});
Response
{
  "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:

SituationResult
trial_end_action: activate_subscription, card on fileThe subscription becomes active. tmbilling raises the first invoice and charges the card.
trial_end_action: activate_subscription, auto-collection offThe 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 methodThe subscription is cancelled with cancel_reason: "no_card". No invoice.
trial_end_action: cancel_subscriptionThe 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:

EventWhen
subscription_trial_ending3 days before trial_end. Remind the customer, or ask for a card.
subscription_activatedThe trial converted.
invoice_generated, invoice_paidThe first invoice was raised and paid.
subscription_canceledThe 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} shows status: "in_trial" and the trial_end you 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) or posted and waiting for payment (auto-collection off).

Common errors

StatusMessageFix
400You can change the trial end only on an in-trial subscription. This subscription is active.The trial already ended. Change the term instead.
400We 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.
400Provide exactly one of trial_end or immediatelySend one of the two fields, not both.
400trial_end must be in the futureSend a later date-time.
400Auto-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.
Copyright © 2026