> ## Documentation Index
> Fetch the complete documentation index at: https://docs.custral.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up billing from code

> Connect Stripe, name your customer key, add products and plans, and open your first customer's billing page, all from your own server.

This guide sets billing up for a workspace without clicking through Settings:
the setup a platform does once per customer workspace, or a script you keep in
your repo. By the end, one of your customers can open a billing page that shows
their plan, card and invoices, and lets them switch plans.

Nothing here charges anyone. Products and plans describe what customers **can**
buy; money moves only when a customer acts on their billing page or a quote.

## Before you start

* A key with the `billing:admin` and `billing:portal` scopes, created in
  **Settings → Applications** by a workspace admin. Setup endpoints refuse a key
  whose creator isn't an admin.
* A Stripe **test-mode** account while you build, so Stripe's test cards work
  and nobody is charged.

## 1. Check where you stand

```ts theme={null}
import {Custral} from "@custral/sdk";

const custral = new Custral({apiKey: process.env.CUSTRAL_API_KEY!});

const setup = await custral.billing.setup();
console.log(setup.ready, setup.missing);
```

`missing` lists what's left, in the order to do it, in words. Run this again
after each step below; when it's empty, you're done.

## 2. Connect Stripe

Stripe asks a person for consent, so an API key can't connect it. Get the link
and have an admin open it:

```ts theme={null}
const {url} = await custral.billing.connectLink();
// Send `url` to a workspace admin. They press Connect Stripe there.
```

Afterwards `setup.stripe` reads `{connected: true, accountName, mode}`. Check
`mode` is `test` before going further.

## 3. Name your customer key

Pick the object that holds your customers in **Settings → Payments** (it
carries a field mapping, so it's set there). Then name the rest by key:

```ts theme={null}
await custral.billing.settings.update({
  customerKey: "account_id",        // the property holding your app's own id
  billingContact: "billing_email",  // who gets the invoices
  allowedReturnDomains: ["app.example.com"],
});
```

Every object and property is named by its **key**, the same one you use in
formulas and imports. `""` clears a setting. See [Payment
settings](/dev/api-reference/billing#payment-settings) for the full list.

## 4. Add products

A product is a record in your Products object. Creating one pushes its price to
Stripe:

```ts theme={null}
await custral.products.create({name: "Growth monthly", price: 99, currency: "usd", interval: "month"});
await custral.products.create({name: "Growth yearly", price: 990, currency: "usd", interval: "year"});
```

Prices are in **major** units. Read them back with `custral.products.list()`;
each product's `stripe` field says whether Stripe has caught up (`synced`).

## 5. Add plans

A plan is what your customers see on their billing page. It names its monthly
and yearly products by id or exact name:

```ts theme={null}
await custral.plans.create({
  key: "growth",
  name: "Growth",
  features: ["Unlimited seats", "Priority support"],
  monthlyProduct: "Growth monthly",
  yearlyProduct: "Growth yearly",
  highlighted: true,
});
```

Set the order customers see them in with
`custral.plans.reorder({keys: ["starter", "growth", "scale"]})`.

## 6. Choose what the page offers

```ts theme={null}
await custral.billing.page.update({
  actions: {updateCard: true, changePlan: true, cancel: true},
  downgradeTiming: "period_end",
  askCancelReason: true,
});
```

An upgrade is always charged straight away; `downgradeTiming` decides whether a
downgrade waits for the period end. See [Billing page
settings](/dev/api-reference/billing#billing-page-settings).

## 7. Open a customer's billing page

```ts theme={null}
const session = await custral.billing.session({
  customer: "acct_123",
  returnUrl: "https://app.example.com/settings",
});
// Redirect the customer to session.url, or mount session.token in your app.
```

The session lasts 15 minutes. To draw the page inside your app instead of
redirecting, see [Give customers a billing
page](/dev/guides/bill-from-your-product#6-give-customers-a-billing-page).

## Doing it from Stella or MCP

The same setup is available as tools: `get_billing_setup`,
`update_billing_settings`, `update_billing_page`, `list_products`,
`create_product`, `update_product`, `list_plans` and `upsert_plan`. The writes follow
the same approval rules as Stella's other write tools, and none of them charges
anyone.

## Common refusals

| Code | Fix |
| - | - |
| `insufficient_permissions` | The key's creator isn't a workspace admin. Create the key as an admin. |
| `products_object_not_mapped` | Pick a Products object in Settings → Payments. |
| `product_field_not_mapped` | Map that field on the Products object in Settings → Payments. |
| `plan_needs_price` | Give the plan a monthly product, a yearly product, or a sales link. |
| `return_url_not_allowed` | Add the host to `allowedReturnDomains`. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.