---
title: "Quickstart"
description: "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."
canonical_url: "https://twominutebilling.com/help/developers/getting-started/quickstart"
---
# 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

<tabs>
<tabs-item icon="i-lucide-cloud" label="Hosted">

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](https://twominutebilling.com/pricing).
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.

</tabs-item>

<tabs-item icon="i-lucide-server" label="Self-hosted">

1. Run the engine and the dashboard with [Docker](https://twominutebilling.com/help/developers/getting-started/self-host-docker),
or from the repository (see `SETUP.md`).
2. Sign in to your dashboard and create a `test` key under **Settings → Developer → API keys**.
3. Your base URL is your own API host, for example `http://localhost:4000` on a local install.

</tabs-item>
</tabs>

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

```bash
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](https://twominutebilling.com/help/developers/getting-started/test-and-live).

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](https://twominutebilling.com/help/developers/getting-started/sdks):

```ts [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.

<code-group>

```ts [TypeScript]
const biz = await tmb.businesses.create({ name: "Fernhill Analytics", default_currency: "USD" });
```

```bash [curl]
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_…
```

</code-group>

```json [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.

<code-group>

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

```bash [curl]
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" }'
```

</code-group>

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

<code-group>

```ts [TypeScript]
const price = await tmb.prices.create(biz.id, {
  product: product.id,
  name: "Pro monthly",
  model: "flat",
  currency: "USD",
  amount_minor: 2900,
  interval: "month",
});
```

```bash [curl]
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"
  }'
```

</code-group>

```json [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](https://twominutebilling.com/help/developers/concepts/catalog).

## Create a customer

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

<code-group>

```ts [TypeScript]
const customer = await tmb.customers.create(biz.id, {
  email: "maya@example.com",
  first_name: "Maya",
  last_name: "Castillo",
  company: "Brightwater Analytics",
  currency: "USD",
});
```

```bash [curl]
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": "maya@example.com",
    "first_name": "Maya",
    "last_name": "Castillo",
    "company": "Brightwater Analytics",
    "currency": "USD"
  }'

export CUS=cus_…
```

</code-group>

```json [Response]
{
  "object": "customer",
  "id": "cus_XXXXXXXXXXXXXXXXXXXXXX01",
  "email": "maya@example.com",
  "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.

<code-group>

```ts [TypeScript]
await tmb.paymentMethods.create(biz.id, customer.id, { token: "4242424242424242" });
```

```bash [curl]
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" }'
```

</code-group>

```json [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](https://twominutebilling.com/help/developers/recipes/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.

<code-group>

```ts [TypeScript]
const subscription = await tmb.subscriptions.create(biz.id, customer.id, {
  items: [{ price: price.id, quantity: 1 }],
});
```

```bash [curl]
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", "quantity": 1 }] }'
```

</code-group>

```json [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](https://twominutebilling.com/help/developers/recipes/trials).

## See the invoice

<code-group>

```ts [TypeScript]
const invoices = await tmb.invoices.list(biz.id, { customer: customer.id });
```

```bash [curl]
curl "$TMBILLING_URL/v1/businesses/$BIZ/invoices?customer=$CUS" \
  -H "Authorization: Bearer $TMBILLING_API_KEY"
```

</code-group>

```json [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

<u-page-grid>
<u-page-card description="Verify signatures, handle retries, and read the event catalogue." icon="i-lucide-webhook" title="Receive webhooks" to="https://twominutebilling.com/help/developers/webhooks">



</u-page-card>

<u-page-card description="Retry declined cards on a schedule, and decide what happens when retries run out." icon="i-lucide-alert-triangle" title="Handle failed payments" to="https://twominutebilling.com/help/developers/recipes/failed-payments">



</u-page-card>

<u-page-card description="Test cards for every failure, the Time Travel, and going live." icon="i-lucide-flask-conical" title="Test and live mode" to="https://twominutebilling.com/help/developers/getting-started/test-and-live">



</u-page-card>

<u-page-card description="Trials, usage-based billing, one-time purchases, India GST and more." icon="i-lucide-chef-hat" title="Recipes" to="https://twominutebilling.com/help/developers/recipes">



</u-page-card>
</u-page-grid>


## Sitemap

See the full [sitemap](https://twominutebilling.com/sitemap.md) for all pages.
