Getting started

Quickstart

Bill your first customer from code in test mode: create a business, a price, a customer with a test card, and a subscription that charges it. No gateway needed.

By the end of this page you have a paying subscription in test mode: a business, a product and price, a customer with a test card, and a subscription whose first invoice is already paid. No real money moves, and you need no payment provider account.

Get a test API key

  1. Sign up at the dashboard's /signup page. You start on the free plan: every feature. No credit card required. It is free while you make less than the Free plan's monthly limit.
  2. Open Settings → Developer. Under API keys, set the mode to test and select Create API key. Copy the secret now: it shows one time only.
  3. Your API base URL is the host that the API reference links at the top of the same page open on.

Export both, so every example on this page runs as written:

export TMBILLING_URL=https://your-api-host
export TMBILLING_API_KEY=sk_test_…

Test keys start with sk_test_. They reach your test businesses only: ask a test key for a live business and you get 404. See Test and live mode.

Each step shows the TypeScript client and curl, then the response. Responses are shortened to the fields that matter here. The API returns every field of the object.

The TypeScript examples use the typed client:

setup.ts
import { Tmbilling } from "@two-minute-billing/sdk";

const tmb = new Tmbilling({
  apiKey: process.env.TMBILLING_API_KEY!,
  baseUrl: process.env.TMBILLING_URL,
});

Create a business

A business holds one catalog, its customers and its money, in one default currency. Every business has a live mode and a test mode. Create one with a test key and you get its test mode back. The live mode is created beside it.

const biz = await tmb.businesses.create({ name: "Fernhill Analytics", default_currency: "USD" });
Response
{
  "object": "business",
  "id": "biz_XXXXXXXXXXXXXXXXXXXXXX01",
  "name": "Fernhill Analytics",
  "livemode": "test",
  "live_business_id": "biz_XXXXXXXXXXXXXXXXXXXXXX02",
  "test_business_id": null,
  "default_currency": "USD",
  "status": "active",
  "created_at": "2026-10-20T19:02:10.255Z"
}

Every URL below is scoped to the business id. Keep it as $BIZ.

Create a product and a price

A product is what you sell. A price is one way to charge for it: a currency, an interval and a pricing model. One product can have many prices — monthly and yearly, USD and EUR.

Catalog objects take a readable id. Omit id and tmbilling derives one from the name, so the product below is pro. Keep the same ids in test and live mode, and the same code works in both.

const product = await tmb.products.create(biz.id, { name: "Pro" });

Now the price. flat charges a fixed amount each period. Amounts are integers in minor units, so 2900 is $29.00.

const price = await tmb.prices.create(biz.id, {
  product: product.id,
  name: "Pro monthly",
  model: "flat",
  currency: "USD",
  amount_minor: 2900,
  interval: "month",
});
Response
{
  "object": "price",
  "id": "pro-monthly",
  "product": "pro",
  "name": "Pro monthly",
  "type": "recurring",
  "model": "flat",
  "currency": "USD",
  "amount_minor": 2900,
  "interval": "month",
  "interval_count": 1,
  "active": true
}

Other models use the same call: per_unit for seats, tiered and volume for graduated pricing, usage for metered billing. See Catalog.

Create a customer

The customer is who pays. Their currency must match the prices you subscribe them to.

const customer = await tmb.customers.create(biz.id, {
  email: "[email protected]",
  first_name: "Maya",
  last_name: "Castillo",
  company: "Brightwater Analytics",
  currency: "USD",
});
Response
{
  "object": "customer",
  "id": "cus_XXXXXXXXXXXXXXXXXXXXXX01",
  "email": "[email protected]",
  "name": "Maya Castillo",
  "company": "Brightwater Analytics",
  "currency": "USD",
  "auto_collection": "on",
  "created_at": "2026-10-20T19:02:10.426Z"
}

Save a test card

Test mode has a built-in test gateway. Give it a test card number as the token and it saves the card. 4242424242424242 always succeeds. Other numbers decline, time out or ask for 3-D Secure on purpose.

await tmb.paymentMethods.create(biz.id, customer.id, { token: "4242424242424242" });
Response
{
  "object": "payment_method",
  "id": "pm_XXXXXXXXXXXXXXXXXXXXXX01",
  "provider": "tmtest",
  "type": "card",
  "brand": "visa",
  "last4": "4242",
  "exp_month": 12,
  "exp_year": 2031,
  "status": "valid",
  "is_default": true,
  "gateway_account_label": "TM Test Gateway"
}

In live mode your checkout or portal collects the card through your payment gateway, and you never handle the number. See Checkout and portal.

Subscribe the customer

A subscription has one item per price, each with a quantity. tmbilling raises the first invoice and charges the saved card straight away.

const subscription = await tmb.subscriptions.create(biz.id, customer.id, {
  items: [{ price: price.id, quantity: 1 }],
});
Response
{
  "object": "subscription",
  "id": "sub_XXXXXXXXXXXXXXXXXXXXXX01",
  "status": "active",
  "currency": "USD",
  "customer": "cus_XXXXXXXXXXXXXXXXXXXXXX01",
  "current_period_start": "2026-10-20T19:02:10.584Z",
  "current_period_end": "2026-11-20T19:02:10.584Z",
  "next_billing_at": "2026-11-20T19:02:10.584Z",
  "mrr": 2900,
  "items": [
    {
      "id": "si_XXXXXXXXXXXXXXXXXXXXXX01",
      "price": "pro-monthly",
      "quantity": 1,
      "unit_price_minor": 2900
    }
  ],
  "latest_invoice": {
    "id": "inv_XXXXXXXXXXXXXXXXXXXXXX01",
    "status": "paid",
    "total_minor": 2900
  }
}

The subscription is active and its first invoice is paid. From now on the billing clock owns it: it renews, invoices and collects each month without another call from you. To start with a free trial instead, see Trials.

See the invoice

const invoices = await tmb.invoices.list(biz.id, { customer: customer.id });
Response
{
  "object": "list",
  "data": [
    {
      "object": "invoice",
      "id": "inv_XXXXXXXXXXXXXXXXXXXXXX01",
      "number": "INV-1",
      "status": "paid",
      "currency": "USD",
      "total_minor": 2900,
      "amount_paid_minor": 2900,
      "amount_due_minor": 0,
      "period_start": "2026-10-20T19:02:10.584Z",
      "period_end": "2026-11-20T19:02:10.584Z",
      "paid_at": "2026-10-20T19:02:10.730Z",
      "subscription": "sub_XXXXXXXXXXXXXXXXXXXXXX01"
    }
  ],
  "has_more": false
}

Every step above also emitted an event. Point a webhook endpoint at your service to receive them as they happen.

Next steps

Receive webhooks
Verify signatures, handle retries, and read the event catalogue.
Handle failed payments
Retry declined cards on a schedule, and decide what happens when retries run out.
Test and live mode
Test cards for every failure, the Time Travel, and going live.
Recipes
Trials, usage-based billing, one-time purchases, India GST and more.
Copyright © 2026