---
title: "Update Discount"
method: PATCH
path: "/discounts/{id}"
tags: ["Discount"]
---

# Update Discount

`PATCH /discounts/{id}`

## Request

- Base URL: `https://myworkspace.myclickfunnels.com/api/v2`
- URL: `https://myworkspace.myclickfunnels.com/api/v2/discounts/{id}`
- Auth: one of:
  - HTTP bearer
  - OAuth 2
    - Authorization code: `https://accounts.myclickfunnels.com/oauth/authorize`
    - Token URL: `https://accounts.myclickfunnels.com/oauth/token`
- TypeScript: <https://skmtc.dev/myclickfunnels/apis/clickfunnels-api/docs/discounts/:id/patch.ts> (a function with typed inputs; its response is checked against the schema)

Update a discount. Money-affecting fields are locked once the discount has been redeemed and return 422, while renaming and expiring still work. A discount that is already expired freezes its other fields and returns 422, a rename included - lift the expiry with `expired: false` (the API side of Remove Expiration in the app) or push `apply_until` into the future, then make the change and expire it again.

## Path parameters

- `id` string, required

## Request body

- object
  - `discount` DiscountParametersUpdate: Fields accepted when updating a discount. Two lock-out rules apply, in this order: once the discount has been redeemed on an order its money-affecting fields (amount, percent, discount_method, applies_to, apply_from, the scoping arrays, limit_1_per_customer, discounts_per_redemption) are locked and return 422, while renaming and expiring still work; a discount that is already expired freezes its other fields and returns 422 (`Discount cannot be changed because it has expired`). Expiring is reversible - `expired: false` clears the end date and a future `apply_until` pushes it out, either of which makes the discount live again - so editing an expired discount means lifting the expiry first. Expiring a discount and renaming it in the same request is fine; it is a rename sent to a discount that is already expired that gets refused.
    - `name` string
    - `code` string: Letters, numbers and hyphens only. Uppercased on save; must be unique within the workspace.
    - `discount_type` 'code' | 'automatic' | 'sale': `code` is a redeemable coupon code. `automatic` and `sale` are deprecated.
    - `discount_method` 'percentage' | 'fixed' | 'shipping' | 'bogo'
    - `percent` string
    - `amount` string
    - `currency` string: ISO currency code. Locked once the discount has been applied to an order.
    - `applies_to` 'all_products' | 'specific_products' | 'specific_variants' | 'specific_collections'
    - `apply_from` string, date-time
    - `apply_until` string, date-time, nullable: Schedule a future end date. Must be after `apply_from`; a past date returns 422. To expire a live discount now, use `expired: true` instead.
    - `expired` boolean: Virtual switch over `apply_until`, accepted on update only (sending it on create returns 422). `true` expires the discount immediately by setting `apply_until` to now; `false` lifts an expiry by clearing `apply_until`, the same as Remove Expiration in the app. It ends a discount that has not started yet (`scheduled: true`) as well, discarding the future `apply_from`, since nothing can end before it starts. A discount that has been redeemed cannot be un-expired and returns 422. Sending the state the discount is already in changes nothing and returns 200 without moving `apply_until`, so echoing a serialized discount back in a routine update cannot clear a scheduled end date - to remove a future end date, PATCH `apply_until` to null instead. Only `true` and `false` are accepted; any other value returns 422 rather than being read as "expire it".
    - `limit_1_per_customer` boolean
    - `require_minimum_spend` boolean
    - `require_minimum_spend_amount` integer: Minimum order subtotal, in whole currency units rather than cents - `50` on a `usd` discount means $50.
    - `discounts_per_redemption` integer
    - `product_ids` integer[]
    - `variant_ids` integer[]
    - `products_collection_ids` integer[]

## Response `200`

OK

- DiscountAttributes: A reusable, workspace-scoped discount applied on top of a price.
  - `id` integer: Discount ID
  - `public_id` string, nullable: Discount public ID
  - `workspace_id` integer: Workspace ID
  - `name` string: Internal name for the discount.
  - `code` string: The code a buyer redeems at checkout. Uppercased on save; unique within the workspace (cannot collide with a coupon code).
  - `discount_type` 'code' | 'automatic' | 'sale': `code` (the default) is a redeemable coupon code. `automatic` and `sale` are deprecated.
  - `discount_method` 'percentage' | 'fixed' | 'shipping' | 'bogo': How the discount is calculated. `percentage` uses `percent`; `fixed` uses `amount`. `shipping` and `bogo` exist but are outside the simple flow.
  - `amount` string, nullable: Fixed amount off, as a decimal string (e.g. "10.00"). `null` for a percentage discount.
  - `percent` string, nullable: Percentage off between 0.01 and 100 (e.g. "20.0"). `null` for a fixed discount.
  - `currency` string: ISO currency code, lowercased. Defaults to the workspace currency.
  - `applies_to` 'all_products' | 'specific_products' | 'specific_variants' | 'specific_collections': Scope of the discount. `all_products` (the default) applies workspace-wide; the `specific_*` values restrict it to the ids passed in `product_ids` / `variant_ids` / `products_collection_ids`.
  - `apply_from` string, date-time: When the discount becomes active. Defaults to the creation time.
  - `apply_until` string, date-time, nullable: When the discount expires. `null` means it never expires.
  - `limit_1_per_customer` boolean: When true, each contact can redeem the discount only once.
  - `require_minimum_spend` boolean: When true, the order must meet `require_minimum_spend_amount` for the discount to apply.
  - `require_minimum_spend_amount` integer, nullable: Minimum order subtotal required, when `require_minimum_spend` is true. In whole currency units, not cents - `50` on a `usd` discount means $50.
  - `discounts_per_redemption` integer, nullable: Optional cap on how many line items a single redemption discounts.
  - `redemptions_count` integer: How many times the discount has been redeemed on an invoice. On Stripe every invoice counts, renewals included; on Payments AI each customer redemption counts once, however many renewal invoices it covers. This is not a deletability signal - a discount can report zero redemptions and still return 422 on DELETE because it is applied to an order that has no invoice yet.
  - `product_ids` integer[]: The products the discount is scoped to (empty unless `applies_to` is `specific_products`).
  - `variant_ids` integer[]: The variants the discount is scoped to (empty unless `applies_to` is `specific_variants`).
  - `products_collection_ids` integer[]: The collections the discount is scoped to (empty unless `applies_to` is `specific_collections`).
  - `active` boolean: Derived - true when the discount is neither scheduled for the future nor expired.
  - `expired` boolean: Derived - true when `apply_until` is in the past. An expired discount is frozen - every further update returns 422, so rename before you expire.
  - `scheduled` boolean: Derived - true when `apply_from` is in the future.
  - `created_at` string, date-time, nullable: Added
  - `updated_at` string, date-time, nullable: Updated

## Other responses

- `401`: Unauthorized
- `403`: Forbidden - the access token's scopes do not cover this request
- `404`: Not Found
- `422`: Unprocessable Entity - validation failed, the discount is locked after redemption, or the discount is expired

## Changes

- **2026-11-03** `511d88b30aea`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/myclickfunnels/apis/clickfunnels-api/changes/discounts/:id/patch.md)

---

[API](https://skmtc.dev/myclickfunnels/apis/clickfunnels-api.md) - [All operations](https://skmtc.dev/myclickfunnels/apis/clickfunnels-api/llms.txt) - [OpenAPI document](https://skmtc.dev/myclickfunnels/apis/clickfunnels-api/revisions/511d88b30aea?raw)
