---
title: "Create a product"
method: POST
path: "/v1/products"
tags: ["Products"]
---

# Create a product

`POST /v1/products`

Creates a product with its pricing. Every product has a `price`; add a `billing_cycle` to make it recurring, or omit it for a one-time product. Sell products through checkout sessions and subscriptions. Requires the `products:write` scope.

## Request body

- CreateProductRequest
  - `name` string, required — Display name of the product. Shown to customers at checkout.
  - `description` string, nullable — Optional description of the product. Shown to customers at checkout.
  - `price` PriceInput, required
    - `currency` 'USD' | 'NGN', required — The product's primary currency. Must be `USD` or `NGN`.
    - `price_type` 'fixed' | 'free' | 'custom' — How the product is priced. `fixed`: a set amount, given in `amount`. `free`: no charge. `custom`: the customer pays what they want, bounded by `minimum_amount` and `maximum_amount` with an optional `preset_amount` suggestion.
    - `amount` string, nullable — Price as a decimal string, e.g. `"29.00"`. Required when `price_type` is `fixed`. Omit for `free` and `custom`.
    - `preset_amount` string, nullable — Suggested amount prefilled at checkout for a custom price, as a decimal string. Only used when `price_type` is `custom`.
    - `minimum_amount` string, nullable — Least the customer can pay, as a decimal string. Only used when `price_type` is `custom`. Set `"0.00"` to allow free (pay what you want).
    - `maximum_amount` string, nullable — Most the customer can pay, as a decimal string. Only used when `price_type` is `custom`.
    - `currency_options` CurrencyOptionInput[], nullable — Prices in other currencies. Each entry sets a price for one additional currency, and cannot repeat the primary currency.
      - `currency` string, required — A supported additional currency (`GHS`, `KES`, `UGX`, `TZS`, `RWF`, `XAF`, `XOF`, `ZMW`). Cannot be the primary currency.
      - `amount` string, nullable — Price as a decimal string, e.g. `"29.00"`. Required when `price_type` is `fixed`. Omit for `free` and `custom`.
      - `preset_amount` string, nullable — Suggested amount prefilled at checkout for a custom price in this currency, as a decimal string. Only used when `price_type` is `custom`.
      - `minimum_amount` string, nullable — Least the customer can pay, as a decimal string. Only used when `price_type` is `custom`. Set `"0.00"` to allow free (pay what you want).
      - `maximum_amount` string, nullable — Most the customer can pay, as a decimal string. Only used when `price_type` is `custom`.
  - `metadata` object, nullable — Up to 20 key/value pairs for your own reference.
  - `billing_cycle` SubscriptionCadence
    - `interval` 'day' | 'week' | 'month' | 'year' — Unit of time for each billing cycle. `day`: billed daily. `week`: billed weekly. `month`: billed monthly. `year`: billed yearly.
    - `frequency` integer — Number of intervals per cycle. For example, `interval` `month` with `frequency` `3` bills every three months.
  - `trial_period` TrialPeriod — The length of the free trial before the first charge, expressed as a count of time units. For example, `{ "interval": "day", "frequency": 14 }` is a 14-day trial.
    - `interval` 'day' | 'week' | 'month' | 'year', required — The unit of time the trial is measured in: `day`, `week`, `month`, or `year`.
    - `frequency` integer, required — How many `interval` units the trial lasts. For example, `interval` `day` with `frequency` `14` is a 14-day trial.

## Response `201`

Product created

- ProductResponse
  - `id` string — Unique identifier for the product, prefixed with `prod_`.
  - `organization_id` string — The organization that owns the product.
  - `name` string — Display name of the product.
  - `description` string, nullable — Optional description of the product. `null` when not set.
  - `price` PriceResponse
    - `currency` string
    - `price_type` 'fixed' | 'free' | 'custom' — How the product is priced. `fixed`: a set amount, given in `amount`. `free`: no charge. `custom`: the customer pays what they want, bounded by `minimum_amount` and `maximum_amount` with an optional `preset_amount` suggestion.
    - `amount` string — Price in the primary currency as a decimal string.
    - `preset_amount` string, nullable — Suggested amount prefilled at checkout for a custom price, as a decimal string. Only used when `price_type` is `custom`.
    - `minimum_amount` string, nullable — Minimum the customer must pay. Present only when `price_type` is `custom`.
    - `maximum_amount` string, nullable — Maximum the customer may pay. Present only when `price_type` is `custom`.
    - `currency_options` CurrencyOptionResponse[]
      - `currency` string
      - `amount` string
      - `minimum_amount` string, nullable
      - `maximum_amount` string, nullable
  - `status` 'active' | 'archived' — Status of the product. `active`: Live and available for use in checkouts and subscriptions. `archived`: Retired. Kept for reference but not available for new purchases.
  - `metadata` object, nullable — Your own key-value data attached to the product, returned unchanged.
  - `media` MediaItemResponse[] — Media items (images) attached to the product. Empty when none are set.
    - `id` string
    - `url` string, nullable
    - `file_name` string
    - `mime_type` string
    - `file_size_bytes` integer
    - `created_at` string, date-time
  - `actor_id` string — Identifier of the user or key that created the product.
  - `created_at` string, date-time — ISO 8601 timestamp when the product was created.
  - `updated_at` string, date-time — ISO 8601 timestamp when the product was last updated.
  - `archived_at` string, date-time, nullable — When set, the product is archived and cannot be used in new checkouts. `null` while the product is active.
  - `billing_cycle` SubscriptionCadence
    - `interval` 'day' | 'week' | 'month' | 'year' — Unit of time for each billing cycle. `day`: billed daily. `week`: billed weekly. `month`: billed monthly. `year`: billed yearly.
    - `frequency` integer — Number of intervals per cycle. For example, `interval` `month` with `frequency` `3` bills every three months.
  - `trial_period` TrialPeriod — The length of the free trial before the first charge, expressed as a count of time units. For example, `{ "interval": "day", "frequency": 14 }` is a 14-day trial.
    - `interval` 'day' | 'week' | 'month' | 'year', required — The unit of time the trial is measured in: `day`, `week`, `month`, or `year`.
    - `frequency` integer, required — How many `interval` units the trial lasts. For example, `interval` `day` with `frequency` `14` is a 14-day trial.
  - `prices` object[] — All prices configured on the product, one per currency. Each has `currency`, `amount`, optional `minimum_amount` and `maximum_amount`, and `is_default`.
  - `total_payments` integer — Running count of completed payments for this product. Starts at `0` and increments as customers pay.
  - `total_amount` string — Running total collected for this product, as a decimal string in the product currency. Starts at `"0.00"`.

## Other responses

- `400` — unresolved $ref
- `401` — Unauthorized - Invalid, missing, or expired API key. Verify your API key is correctly formatted and included in the Authorization header as `Bearer sk_sandbox_...` or `Bearer sk_live_...`. Check that your key hasn't been revoked.
- `403` — Forbidden - API key does not have permission for the requested resource. Verify you're using the correct organization's API key and that you're not trying to access another organization's data.

---

[API](https://skmtc.dev/bachs/apis/bachs-api.md) · [All operations](https://skmtc.dev/bachs/apis/bachs-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/bachs/bachs-api/revisions/f29efa8a22df/schema)
