---
id: docs.products-and-prices
title: "Products and prices"
canonical: https://docs.secureprocessing.app/products-and-prices
last_verified: 2026-11-06
---

# Products and prices

A catalogue: the things you sell, and what they cost.

A **product** is the thing. A **price** is an amount in a currency, attached to a product. They are separate because a product outlives any one amount — when you put your prices up, the old price is archived rather than edited, and anything already quoting it keeps quoting it.

> **Warning:** **Amounts are integers in minor units.** The field is called `unit_amount` to match what you already know, but `6000` is $60.00, not sixty dollars. No decimal crosses this API in either direction.

## Create a product and a price

```bash
curl https://api.secureprocessing.app/v1/products \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Blue Hole Two-Tank Dive"}'

curl https://api.secureprocessing.app/v1/prices \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
        "product": "prod_8kM2nP4qR6sT8uV0wX2yZ4aB",
        "currency": "usd",
        "unit_amount": 6000
      }'
```

```ts
const product = await sp.products.create({ name: "Blue Hole Two-Tank Dive" });

const price = await sp.prices.create({
  product: product.id,
  currency: "usd",
  unit_amount: 6000, // minor units — $60.00
});
```

## A price cannot be edited

Only `active`, `nickname` and `metadata` can be changed. The amount, the currency and the product are fixed for the life of the price, and an attempt to change one is refused rather than ignored.

The reason is that a price id is what gets stored elsewhere. If the amount behind an id could move, editing a typo would change what every link already in circulation charges — including one a customer has open right now — and there would be nothing in the record saying it had ever been anything else. To change what something costs, create a new price and archive the old one.

```ts
const replacement = await sp.prices.create({
  product: product.id,
  currency: "usd",
  unit_amount: 6500,
});

await sp.prices.update(price.id, { active: false, nickname: "2026 rate" });
```

## Let the payer choose the amount

For a donation, a deposit or a tip, give a range instead of a fixed amount. All three figures are minor units.

```ts
await sp.prices.create({
  product: donation.id,
  currency: "bzd",
  custom_unit_amount: {
    minimum: 500,     // $5.00
    maximum: 100000,  // $1,000.00
    preset: 2500,     // what the field is filled in with
  },
});
```

A price has exactly one of `unit_amount` or `custom_unit_amount`. Neither is a price that charges nothing; both is a price with two answers to what it costs. Either is refused.

## Reading them back

A product does not embed its prices. List them with the `product` filter, so a product with two hundred prices is a page you asked for rather than a response that surprises you.

```bash
curl "https://api.secureprocessing.app/v1/prices?product=prod_8kM2nP4qR6sT8uV0wX2yZ4aB&active=true" \
  -H "Authorization: Bearer sk_test_..."
```

## Archiving

Nothing here is ever deleted. `active: false` is the archive, and archiving a product deliberately leaves its prices alone — a payment made months ago has to stay readable, with the thing it was for still resolvable.

## Events

| Event | When |
|---|---|
| product.created | A product was added. |
| product.updated | A product was edited or archived. |
| price.created | A price was created. |
| price.updated | A price was renamed, archived or re-tagged. Never repriced. |
