Variants

Create Variant

Create a new pricing variant for a product. The variant defines the billing interval, price, and availability for customers.

post/variants

Headers

Idempotency-Keystring
Example:d9105228-4a08-46b1-8b91-42fed586d383

A unique key that makes this request safe to retry. See Idempotent requests.

Request body

account_idstring

The unique identifier of the account to create this variant for. Required when authenticating as a user; an account API key supplies its own account.

adaptive_pricing_enabledboolean nullable

Whether this variant accepts local currency payments via adaptive pricing.

attributesobject nullable

Attribute values that make this variant one variant of its product, as a map of attribute name to value, e.g. {"size": "Large", "color": "Blue"}. Names are normalized to snake_case identifiers (Ring Size becomes ring_size) and come back in alphabetical order. Every variant on a product must carry the same attribute names and a distinct set of values. Send null to make the variant an ordinary pricing option again.

billing_periodinteger nullable

Recurring billing interval in days, such as 30 for monthly or 365 for annual.

checkout_stylingobject nullable

Checkout styling overrides for this variant.

currencystring

The three-letter ISO currency code for the variant's pricing. Defaults to USD.

descriptionstring nullable

A text description of the variant displayed to customers on the product page.

expiration_daysinteger nullable

Access duration in days before the membership expires.

initial_pricenumber nullable

Initial amount charged in the variant's currency, e.g. 10.43 for $10.43. A paid fiat variant charges at least 1.00 in its currency; use 0 for free.

internal_notesstring nullable

Private notes visible only to the account owner. Not shown to customers.

metadataobject nullable

Custom key-value pairs to store on the variant. Included in webhook payloads for payment and membership events. Max 50 keys, 100 chars per key, 500 chars per string value. The reserved keys custom_cta (a checkout call-to-action button label — one of the product custom CTA values, e.g. subscribe, get_offer) and custom_cta_url (a URL the button links to; web or tel:) override the product's call to action for this variant and are validated on save.

override_tax_typestring

Override the default tax classification for this specific variant.

plan_typestring

Variant billing type, such as one_time or renewal.

product_idstring

The unique identifier of the product to attach this variant to.

release_methodstring

Sales method for this variant.

renewal_pricenumber nullable

The amount charged each billing period for recurring variants, in the variant's currency. A paid fiat variant charges at least 1.00 in its currency.

skustring nullable

Stock keeping unit for this variant. Maximum 100 characters. Free text, not enforced unique.

split_pay_required_paymentsinteger nullable

Installment payments required before the subscription pauses.

stockinteger nullable

The maximum number of units available for purchase. Ignored when unlimited_stock is true.

three_ds_level'mandate_challenge' | 'mandate_if_required' | 'frictionless_if_required' | 'null' nullable

3D Secure behavior for supported on-session card payments. mandate_challenge requires a 3DS challenge before payment processing; mandate_if_required mandates a challenge only when the payment processor requires it; frictionless_if_required uses the regular frictionless 3DS flow. Payments of $1,000 or more use mandate_if_required unless mandate_challenge is selected. Risk and authentication recovery requirements can override the preference. Send null to inherit the account default.

titlestring nullable

The display name of the variant shown to customers on the product page. Maximum 30 characters.

trial_period_daysinteger nullable

Free trial duration before the first recurring charge.

unlimited_stockboolean nullable

Whether the variant has unlimited stock. When true, the stock field is ignored.

visibilitystring

Whether the variant is visible to customers or hidden from public view.

Example request

{
  "account_id": "biz_xxxxxxxxxxxxxx",
  "adaptive_pricing_enabled": true,
  "attributes": {
    "color": "Blue",
    "size": "Large"
  },
  "billing_period": 30,
  "checkout_styling": {
    "background_color": "#0f172a",
    "border_style": "rounded",
    "button_color": "#f59e0b",
    "font_family": "roboto"
  },
  "currency": "usd",
  "custom_fields": [
    {
      "field_type": "text",
      "id": "field_xxxxxxxxxxxxxx",
      "name": "Vehicle make and model",
      "order": 1,
      "placeholder": "2021 Audi S5",
      "required": true
    }
  ],
  "description": "Two hand washes a month, interior vacuum, and a quarterly sealant top-up.",
  "expiration_days": 365,
  "image": {
    "direct_upload_id": "eyJfcmFpbHMiOnsiZGF0YSI6MSwicHVyIjoiYmxvYl9pZCJ9fQ==--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "id": "file_xxxxxxxxxxxxxx"
  },
  "internal_notes": "Maintenance tier. Upsell the interior shampoo add-on at renewal.",
  "metadata": {
    "bay": "2",
    "custom_cta": "subscribe",
    "custom_cta_url": "https://shinetime.example/wash-club",
    "route": "north-austin"
  },
  "override_tax_type": "inclusive",
  "payment_method_configuration": {
    "disabled": [
      "paypal"
    ],
    "enabled": [
      "card"
    ],
    "include_platform_defaults": true
  },
  "plan_type": "renewal",
  "product_id": "prod_xxxxxxxxxxxxxx",
  "release_method": "buy_now",
  "renewal_price": 59,
  "sku": "TEE-LARGE-BLUE",
  "split_pay_required_payments": 4,
  "stock": 25,
  "three_ds_level": "frictionless_if_required",
  "title": "Unlimited Wash Club",
  "trial_period_days": 7,
  "visibility": "visible"
}

Response

variant created

adaptive_pricing_enabledboolean required

Whether adaptive pricing is enabled for this variant. Raw setting — does not check processor compatibility or feature flags.

attributesobject nullable required

Attribute values that distinguish this variant within its product, as a map of attribute name to value, e.g. {"color": "Blue", "size": "Large"}. Names are snake_case identifiers and come back in alphabetical order. Every attributed variant on a product carries the same attribute names and a distinct set of values; the product lists the full option set as variant_attributes. null when the variant has no attributes.

billing_periodnumber nullable required

Number of days between recurring charges, such as 30 for monthly or 365 for annual. null for one-time variants.

cancel_discount_intervalsnumber nullable required

Billing intervals the cancellation discount applies to (0 forever, 1 first payment, or a month count). null when none is offered or the actor lacks the plan:basic:read scope.

cancel_discount_percentagenumber nullable required

Cancellation discount as a whole-number percentage. null when none is offered or the actor lacks the plan:basic:read scope.

checkout_stylingobject nullable required

Variant-level checkout styling (background_color, button_color, font_family, border_style); null inherits the account default.

collect_taxboolean required

Whether tax is collected on purchases of this variant, based on the account's tax configuration.

created_atstring required

When the variant was created, as an ISO 8601 timestamp.

currencystring required

Three-letter ISO currency code for this variant's prices.

deletableboolean nullable required

Whether the variant can be deleted (it has no memberships or waitlist entries). null unless the actor has the plan:basic:read scope on the variant's account.

descriptionstring nullable required

Customer-visible variant description. Maximum 1000 characters. null if no description is set.

expiration_daysnumber nullable required

Access duration in days for expiration-based variants, such as 365 for a one-year pass. null for variants without an expiration.

formatted_pricestring required

Human-readable price for display (currency + interval), e.g. "$10 / month".

idstring required

Variant ID, prefixed plan_.

imageobject nullable required

Pricing-tier image (url, blurhash) shown on the product page; null when no image is set.

initial_pricenumber required

Initial purchase price in variant currency.

internal_notesstring nullable required

Private notes not shown to customers. null unless the actor has the plan:basic:read scope on the variant's account.

invoiceobject nullable required

Invoice this variant was generated for; null unless created for an invoice.

member_countnumber nullable required

Active memberships through this variant. null unless the actor has the plan:basic:read scope on the variant's account.

metadataobject nullable required

Custom key-value pairs stored on the variant. Included in webhook payloads for payment and membership events. Maximum 50 keys, 100 characters per key, 500 characters per value. The reserved keys custom_cta and custom_cta_url, when set, override the product's checkout call to action for this variant.

offer_cancel_discountboolean nullable required

Whether a cancellation discount is offered. null unless the actor has the plan:basic:read scope on the variant's account.

payment_method_configurationobject nullable required

Payment method configuration (enabled, disabled, include_platform_defaults); null when variant uses default settings.

plan_type'renewal' | 'one_time' required

Billing model for this variant.

productobject nullable required

Product this variant belongs to; null for standalone variants.

purchase_urlstring required

URL where customers can purchase this variant directly.

release_method'buy_now' | 'waitlist' required

Sales method for this variant.

renewal_pricenumber required

Recurring price charged every billing period.

skustring nullable required

Stock keeping unit, free text set by the seller (e.g. TSHIRT-LARGE-BLUE). Not enforced unique. null when unset.

split_pay_required_paymentsnumber nullable required

Installment payments required before the subscription pauses. Must be greater than 1. null if split pay is not configured.

stocknumber nullable required

Units available for purchase. null unless the actor has the plan:basic:read scope on the variant's account.

strike_through_initial_pricenumber nullable required

Original initial price shown with a strikethrough, in the variant's currency. null when no strikethrough is set.

strike_through_renewal_pricenumber nullable required

Original renewal price shown with a strikethrough, in the variant's currency. null when no strikethrough is set.

tax_type'inclusive' | 'exclusive' | 'unspecified' required

How tax is handled for this variant, including whether tax is included in the price, added at checkout, or not configured.

three_ds_level'mandate_challenge' | 'mandate_if_required' | 'frictionless_if_required' | 'null' nullable required

3D Secure behavior for supported on-session card payments. mandate_challenge requires a 3DS challenge before payment processing; mandate_if_required mandates a challenge only when the payment processor requires it; frictionless_if_required uses the regular frictionless 3DS flow. Payments of $1,000 or more use mandate_if_required unless mandate_challenge is selected. Risk and authentication recovery requirements can override the preference. null inherits the account default.

titlestring nullable required

Variant display name shown to customers. Maximum 30 characters. A variant created without one defaults to its attribute values joined with /. null if no title has been set.

trial_period_daysnumber nullable required

Free trial days before the first renewal charge. null if no trial is configured or the user has already used a trial for this variant.

unlimited_stockboolean required

Whether the variant has unlimited stock. When true, the stock field is ignored; waitlist variants always report true.

updated_atstring required

When the variant was last updated, as an ISO 8601 timestamp.

visibility'visible' | 'hidden' | 'archived' | 'quick_link' required

Controls where this variant can be seen. When hidden, the variant is reachable only by its direct link.

Example response

{
  "account": {
    "id": "biz_xxxxxxxxxxxxxx",
    "title": "Shine Time Auto Detailing"
  },
  "adaptive_pricing_enabled": true,
  "attributes": {
    "color": "Blue",
    "size": "Large"
  },
  "billing_period": 30,
  "cancel_discount_intervals": 3,
  "cancel_discount_percentage": 20,
  "checkout_styling": {
    "background_color": "#0f172a",
    "border_style": "rounded",
    "button_color": "#f59e0b",
    "font_family": "roboto"
  },
  "created_at": "2026-01-01T12:00:00.000Z",
  "currency": "usd",
  "custom_fields": [
    {
      "field_type": "text",
      "id": "field_xxxxxxxxxxxxxx",
      "name": "Vehicle make and model",
      "placeholder": "2021 Audi S5",
      "required": true
    }
  ],
  "deletable": true,
  "description": "Two hand washes a month, interior vacuum, and a quarterly sealant top-up.",
  "effective_payment_method_configuration": {
    "disabled": [
      "card"
    ],
    "enabled": [
      "card"
    ],
    "include_platform_defaults": true
  },
  "expiration_days": 365,
  "formatted_price": "$59.00 / month",
  "id": "plan_xxxxxxxxxxxxxx",
  "image": {
    "blurhash": "LA6bDXT$E3b:R6i+RibEIWbp%ej1",
    "url": "https://whop-assets-example.s3.amazonaws.com/uploads/image/2026-01-01/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  "initial_price_due": {
    "amount": "-1234.56",
    "currency": "usd",
    "decimals": 2,
    "display_decimals": 2
  },
  "internal_notes": "Maintenance tier. Upsell the interior shampoo add-on at renewal.",
  "invoice": {
    "id": "inv_xxxxxxxxxxxxxx"
  },
  "metadata": {
    "custom_cta": "subscribe",
    "custom_cta_url": "https://shinetime.example/wash-club"
  },
  "offer_cancel_discount": true,
  "payment_method_configuration": {
    "disabled": [
      "crypto"
    ],
    "enabled": [
      "card"
    ],
    "include_platform_defaults": true
  },
  "plan_type": "renewal",
  "product": {
    "id": "prod_xxxxxxxxxxxxxx",
    "title": "Ceramic Coating Package"
  },
  "purchase_url": "https://whop.com/checkout/plan_xxxxxxxxxxxxxx",
  "release_method": "buy_now",
  "renewal_price": 59,
  "sku": "WASH-CLUB-TEE-LARGE-BLUE",
  "split_pay_required_payments": 4,
  "strike_through_initial_price": 99,
  "strike_through_renewal_price": 79,
  "tax_type": "unspecified",
  "three_ds_level": "frictionless_if_required",
  "title": "Unlimited Wash Club",
  "trial_period_days": 7,
  "updated_at": "2026-01-01T12:00:00.000Z",
  "visibility": "visible"
}

Changes

Changed in 1 of the 77 revisions of this API.1

Of the 77 revisions, 1 has no diff computed.