# Create a product (/en/api-reference/products/createProduct)

Creates a new product.

Optionally includes:
- An image URL (use the `/upload-url` endpoint to get a one-time upload URL first)
- A default price object which will be set as the default price for this product

## POST /products

### Request body (required)

Product data. The image field should be a URL string from Cloudflare Images.

Content type: `application/json`

- `name` (string, required) — Product name
- `description` (string) — Product description
- `image` (string) — Product image URL
- `unitLabel` (string) — Unit label (e.g., 'user', 'GB', 'month')
- `active` (boolean) — Whether the product is active
- `metadata` (object) — Custom metadata
- `defaultPrice` (CreateProductDefaultPrice) — Default price object. This Price will be set as the default price for this product.
  - `name` (string) — Price name
  - `description` (string) — Price description
  - `currency` (enum("EGP" | "USD" | "EUR" | "GBP" | "SAR" | "AED" | "QAR" | "KWD" | "JOD" | "OMR" | "BHD" | "LYD" | "AUD" | "CAD" | "CNY")) — Currency
  - `unitAmount` (number) — Unit amount in smallest currency unit (e.g., piasters for EGP). Required unless `type` is CUSTOM. May be 0.
  - `type` (enum("ONE_TIME" | "RECURRING" | "CUSTOM")) — Price type
  - `recurring` (object) — Recurring configuration (required for RECURRING type)
  - `customUnitAmount` (CustomUnitAmountInput) — When set, provides configuration for the amount to be adjusted by the customer during Checkout Sessions and Payment Links. Required unless unit_amount is provided
    - `minimum` (number) — Minimum amount the customer may enter (minor units).
    - `maximum` (number) — Maximum amount the customer may enter (minor units).
    - `preset` (number) — Suggested amount pre-filled for the customer (minor units). Must fall within [minimum, maximum] when both bounds are set.
  - `stock` (number | null) — Remaining stock for the default price. Omit or set `null` for unlimited.
  - `startDate` (string) — Start date (before which the price is not usable in checkout)
  - `expirationDate` (string) — Expiration date (at/after which the price is not usable in checkout)
  - `active` (boolean) — Whether the price is active
  - `metadata` (object) — Custom metadata

### Responses

#### 201 — Product created successfully

Content type: `application/json`

- `id` (string, required) — Product ID
- `name` (string, required) — Product name
- `description` (string | null) — Product description
- `image` (string | null) — Product image URL
- `unitLabel` (string | null) — Unit label
- `active` (boolean, required) — Whether the product is active
- `isSystemCreated` (boolean, required) — Whether the product was created automatically by the system
- `metadata` (object | null) — Custom metadata
- `prices` (Array<PriceListItem>, required) — Product prices
  - `id` (string, required) — Price ID
  - `unitAmount` (number | null) — Unit amount in smallest currency unit (e.g., cents for EGP)
  - `currency` (enum("EGP" | "USD" | "EUR" | "GBP" | "SAR" | "AED" | "QAR" | "KWD" | "JOD" | "OMR" | "BHD" | "LYD" | "AUD" | "CAD" | "CNY"), required) — Currency
  - `type` (enum("ONE_TIME" | "RECURRING" | "CUSTOM"), required) — Price type
  - `recurring` (RecurringConfig) — Recurring configuration (`interval` + `intervalCount`). Present only when `type` is `RECURRING`; `null` for `ONE_TIME` and `CUSTOM` prices.
    - `interval` (enum("DAY" | "WEEK" | "MONTH" | "YEAR" | "CUSTOM")) — Interval type for recurring prices
    - `intervalCount` (number) — Number of intervals between each billing cycle
  - `customUnitAmount` (CustomUnitAmountResponse) — Custom unit amount configuration — present for CUSTOM-type prices. Bounds and preset are in minor units.
    - `minimum` (number | null) — Minimum amount the customer may enter (minor units).
    - `maximum` (number | null) — Maximum amount the customer may enter (minor units).
    - `preset` (number | null) — Suggested amount pre-filled for the customer (minor units).
  - `stock` (number | null) — Remaining stock. `null` means unlimited. Decremented on PaymentIntent success. May become negative under race conditions — treat negative as oversold.
  - `createdAt` (string, required) — Creation timestamp (ISO 8601)
- `createdAt` (string, required) — Creation timestamp (ISO 8601)
- `updatedAt` (string, required) — Last update timestamp (ISO 8601)

#### 400 — Invalid product payload: missing `name`, invalid `defaultPrice`, or malformed image URL.

Content type: `application/json`

- `error` (ApiErrorBody, required) — The structured error body. See `ApiErrorBody` for field semantics.
  - `type` (enum("invalid_request_error" | "authentication_error" | "rate_limit_error" | "api_error" | "card_error" | "idempotency_error"), required) — High-level error category. Use this for branching at the top level of your error handler (e.g. retry on `api_error`, surface a card decline on `card_error`).
  - `code` (enum("invalid_request" | "parameter_out_of_range" | "parameter_invalid" | "parameter_missing" | "parameter_unknown" | "parameters_exclusive" | "parameter_requires_another" | "validation_error" | "resource_missing" | "resource_invalid_state" | "resource_already_exists" | "resource_in_use" | "authentication_required" | "invalid_api_key" | "api_key_inactive" | "invalid_signature" | "merchant_not_activated" | "permission_denied" | "two_factor_required" | "checkout_session_expired" | "invalid_client_secret" | "creation_failed" | "payment_still_confirming" | "payment_already_completed" | "payment_link_inactive" | "payment_link_expired" | "amount_invalid" | "currency_invalid" | "product_archived" | "price_inactive" | "price_not_yet_active" | "price_expired" | "price_sold_out" | "price_date_range_invalid" | "price_stock_invalid" | "price_recurring_not_supported" | "price_immutable_while_used" | "line_item_missing_price" | "checkout_empty_cart" | "promotion_codes_not_allowed" | "promotion_code_not_found" | "promotion_code_inactive" | "promotion_code_expired" | "promotion_code_max_redemptions" | "promotion_code_customer_mismatch" | "promotion_code_minimum_amount" | "promotion_code_first_time_only" | "coupon_invalid" | "coupon_currency_mismatch" | "coupon_minimum_amount" | "coupon_customer_max_redemptions" | "too_many_discounts" | "payment_method_corrupted" | "payment_method_customer_mismatch" | "payment_intent_customer_mismatch" | "payment_method_mismatch" | "charge_not_captured" | "merchant_no_balance" | "insufficient_balance" | "charge_missing_balance_transaction" | "charge_missing_fee_data" | "charge_incomplete_fee_data" | "cannot_rename_default" | "must_have_enabled_method" | "cannot_delete_default" | "configuration_in_use" | "unsupported_currency" | "exchange_rate_not_found" | "coupon_in_use" | "promotion_code_exists" | "rate_limit" | "idempotency_key_in_use" | "internal_error" | "request_timeout")) — Stable, machine-readable code identifying the specific failure (e.g. `resource_missing`, `parameter_invalid`, `authentication_required`). Always present on documented errors. See the API Error Codes reference for the full list.
  - `message` (string, required) — Human-readable description of what went wrong. Safe to log or display, but not stable — use `code` for programmatic branching.
  - `param` (string) — Name of the request parameter that caused the error, when applicable (e.g. `amount`, `currency`).
  - `doc_url` (string) — URL to the documentation page for this specific error code. Deep-linked into the API Error Codes reference.
- `request_id` (string) — Unique identifier for this request, propagated to logs and traces. Include it when contacting support.

#### 401 — Authentication is required and the request did not present a valid API key.

Content type: `application/json`

- `error` (ApiErrorBody, required) — The structured error body. See `ApiErrorBody` for field semantics.
  - `type` (enum("invalid_request_error" | "authentication_error" | "rate_limit_error" | "api_error" | "card_error" | "idempotency_error"), required) — High-level error category. Use this for branching at the top level of your error handler (e.g. retry on `api_error`, surface a card decline on `card_error`).
  - `code` (enum("invalid_request" | "parameter_out_of_range" | "parameter_invalid" | "parameter_missing" | "parameter_unknown" | "parameters_exclusive" | "parameter_requires_another" | "validation_error" | "resource_missing" | "resource_invalid_state" | "resource_already_exists" | "resource_in_use" | "authentication_required" | "invalid_api_key" | "api_key_inactive" | "invalid_signature" | "merchant_not_activated" | "permission_denied" | "two_factor_required" | "checkout_session_expired" | "invalid_client_secret" | "creation_failed" | "payment_still_confirming" | "payment_already_completed" | "payment_link_inactive" | "payment_link_expired" | "amount_invalid" | "currency_invalid" | "product_archived" | "price_inactive" | "price_not_yet_active" | "price_expired" | "price_sold_out" | "price_date_range_invalid" | "price_stock_invalid" | "price_recurring_not_supported" | "price_immutable_while_used" | "line_item_missing_price" | "checkout_empty_cart" | "promotion_codes_not_allowed" | "promotion_code_not_found" | "promotion_code_inactive" | "promotion_code_expired" | "promotion_code_max_redemptions" | "promotion_code_customer_mismatch" | "promotion_code_minimum_amount" | "promotion_code_first_time_only" | "coupon_invalid" | "coupon_currency_mismatch" | "coupon_minimum_amount" | "coupon_customer_max_redemptions" | "too_many_discounts" | "payment_method_corrupted" | "payment_method_customer_mismatch" | "payment_intent_customer_mismatch" | "payment_method_mismatch" | "charge_not_captured" | "merchant_no_balance" | "insufficient_balance" | "charge_missing_balance_transaction" | "charge_missing_fee_data" | "charge_incomplete_fee_data" | "cannot_rename_default" | "must_have_enabled_method" | "cannot_delete_default" | "configuration_in_use" | "unsupported_currency" | "exchange_rate_not_found" | "coupon_in_use" | "promotion_code_exists" | "rate_limit" | "idempotency_key_in_use" | "internal_error" | "request_timeout")) — Stable, machine-readable code identifying the specific failure (e.g. `resource_missing`, `parameter_invalid`, `authentication_required`). Always present on documented errors. See the API Error Codes reference for the full list.
  - `message` (string, required) — Human-readable description of what went wrong. Safe to log or display, but not stable — use `code` for programmatic branching.
  - `param` (string) — Name of the request parameter that caused the error, when applicable (e.g. `amount`, `currency`).
  - `doc_url` (string) — URL to the documentation page for this specific error code. Deep-linked into the API Error Codes reference.
- `request_id` (string) — Unique identifier for this request, propagated to logs and traces. Include it when contacting support.

#### 403 — The credential is valid but lacks the permission this endpoint requires. Check the API key's allowed permissions in the dashboard.

Content type: `application/json`

- `error` (ApiErrorBody, required) — The structured error body. See `ApiErrorBody` for field semantics.
  - `type` (enum("invalid_request_error" | "authentication_error" | "rate_limit_error" | "api_error" | "card_error" | "idempotency_error"), required) — High-level error category. Use this for branching at the top level of your error handler (e.g. retry on `api_error`, surface a card decline on `card_error`).
  - `code` (enum("invalid_request" | "parameter_out_of_range" | "parameter_invalid" | "parameter_missing" | "parameter_unknown" | "parameters_exclusive" | "parameter_requires_another" | "validation_error" | "resource_missing" | "resource_invalid_state" | "resource_already_exists" | "resource_in_use" | "authentication_required" | "invalid_api_key" | "api_key_inactive" | "invalid_signature" | "merchant_not_activated" | "permission_denied" | "two_factor_required" | "checkout_session_expired" | "invalid_client_secret" | "creation_failed" | "payment_still_confirming" | "payment_already_completed" | "payment_link_inactive" | "payment_link_expired" | "amount_invalid" | "currency_invalid" | "product_archived" | "price_inactive" | "price_not_yet_active" | "price_expired" | "price_sold_out" | "price_date_range_invalid" | "price_stock_invalid" | "price_recurring_not_supported" | "price_immutable_while_used" | "line_item_missing_price" | "checkout_empty_cart" | "promotion_codes_not_allowed" | "promotion_code_not_found" | "promotion_code_inactive" | "promotion_code_expired" | "promotion_code_max_redemptions" | "promotion_code_customer_mismatch" | "promotion_code_minimum_amount" | "promotion_code_first_time_only" | "coupon_invalid" | "coupon_currency_mismatch" | "coupon_minimum_amount" | "coupon_customer_max_redemptions" | "too_many_discounts" | "payment_method_corrupted" | "payment_method_customer_mismatch" | "payment_intent_customer_mismatch" | "payment_method_mismatch" | "charge_not_captured" | "merchant_no_balance" | "insufficient_balance" | "charge_missing_balance_transaction" | "charge_missing_fee_data" | "charge_incomplete_fee_data" | "cannot_rename_default" | "must_have_enabled_method" | "cannot_delete_default" | "configuration_in_use" | "unsupported_currency" | "exchange_rate_not_found" | "coupon_in_use" | "promotion_code_exists" | "rate_limit" | "idempotency_key_in_use" | "internal_error" | "request_timeout")) — Stable, machine-readable code identifying the specific failure (e.g. `resource_missing`, `parameter_invalid`, `authentication_required`). Always present on documented errors. See the API Error Codes reference for the full list.
  - `message` (string, required) — Human-readable description of what went wrong. Safe to log or display, but not stable — use `code` for programmatic branching.
  - `param` (string) — Name of the request parameter that caused the error, when applicable (e.g. `amount`, `currency`).
  - `doc_url` (string) — URL to the documentation page for this specific error code. Deep-linked into the API Error Codes reference.
- `request_id` (string) — Unique identifier for this request, propagated to logs and traces. Include it when contacting support.

#### 500 — Something went wrong on our end. Safe to retry idempotent requests; for non-idempotent calls, consult the `request_id` before resubmitting.

Content type: `application/json`

- `error` (ApiErrorBody, required) — The structured error body. See `ApiErrorBody` for field semantics.
  - `type` (enum("invalid_request_error" | "authentication_error" | "rate_limit_error" | "api_error" | "card_error" | "idempotency_error"), required) — High-level error category. Use this for branching at the top level of your error handler (e.g. retry on `api_error`, surface a card decline on `card_error`).
  - `code` (enum("invalid_request" | "parameter_out_of_range" | "parameter_invalid" | "parameter_missing" | "parameter_unknown" | "parameters_exclusive" | "parameter_requires_another" | "validation_error" | "resource_missing" | "resource_invalid_state" | "resource_already_exists" | "resource_in_use" | "authentication_required" | "invalid_api_key" | "api_key_inactive" | "invalid_signature" | "merchant_not_activated" | "permission_denied" | "two_factor_required" | "checkout_session_expired" | "invalid_client_secret" | "creation_failed" | "payment_still_confirming" | "payment_already_completed" | "payment_link_inactive" | "payment_link_expired" | "amount_invalid" | "currency_invalid" | "product_archived" | "price_inactive" | "price_not_yet_active" | "price_expired" | "price_sold_out" | "price_date_range_invalid" | "price_stock_invalid" | "price_recurring_not_supported" | "price_immutable_while_used" | "line_item_missing_price" | "checkout_empty_cart" | "promotion_codes_not_allowed" | "promotion_code_not_found" | "promotion_code_inactive" | "promotion_code_expired" | "promotion_code_max_redemptions" | "promotion_code_customer_mismatch" | "promotion_code_minimum_amount" | "promotion_code_first_time_only" | "coupon_invalid" | "coupon_currency_mismatch" | "coupon_minimum_amount" | "coupon_customer_max_redemptions" | "too_many_discounts" | "payment_method_corrupted" | "payment_method_customer_mismatch" | "payment_intent_customer_mismatch" | "payment_method_mismatch" | "charge_not_captured" | "merchant_no_balance" | "insufficient_balance" | "charge_missing_balance_transaction" | "charge_missing_fee_data" | "charge_incomplete_fee_data" | "cannot_rename_default" | "must_have_enabled_method" | "cannot_delete_default" | "configuration_in_use" | "unsupported_currency" | "exchange_rate_not_found" | "coupon_in_use" | "promotion_code_exists" | "rate_limit" | "idempotency_key_in_use" | "internal_error" | "request_timeout")) — Stable, machine-readable code identifying the specific failure (e.g. `resource_missing`, `parameter_invalid`, `authentication_required`). Always present on documented errors. See the API Error Codes reference for the full list.
  - `message` (string, required) — Human-readable description of what went wrong. Safe to log or display, but not stable — use `code` for programmatic branching.
  - `param` (string) — Name of the request parameter that caused the error, when applicable (e.g. `amount`, `currency`).
  - `doc_url` (string) — URL to the documentation page for this specific error code. Deep-linked into the API Error Codes reference.
- `request_id` (string) — Unique identifier for this request, propagated to logs and traces. Include it when contacting support.