Skip to main content
Plan offer endpoints let you configure which subscription frequencies and discounts are available for each product or variant. You can list all offers, fetch a single offer’s full detail including the effective configuration resolved from variant-to-product fallback semantics, create or upsert offers, update existing records, and toggle enabled state without touching other fields. All routes require an authenticated Medusa Admin user. All mutations are workflow-backed and return the refreshed detail payload.
All routes require an authenticated Medusa Admin user. Unauthenticated requests return 401.

Domain values

DomainSupported values
Statusenabled, disabled
Scopeproduct, variant
Frequency intervalweek, month, year
Discount typepercentage, fixed
Stacking policyallowed, disallow_all, disallow_subscription_discounts

GET /admin/subscription-offers

Returns the paginated plan offer list for Admin DataTable views.

Query parameters

limit
number
Number of results per page.
offset
number
Zero-based result offset for pagination.
q
string
Free-text search across name and product/variant titles.
order
string
Field to sort by. Database-backed: name, scope, is_enabled, created_at, updated_at. In-memory: status, product_title, variant_title.
direction
string
Sort direction. One of asc or desc.
is_enabled
boolean
Filter by enabled/disabled state.
scope
string
Filter by target scope. One of product or variant.
product_id
string
Filter by product ID.
variant_id
string
Filter by variant ID.
frequency
string
Filter by frequency interval. One of week, month, or year.
discount_min
number
Minimum discount value filter.
discount_max
number
Maximum discount value filter.

Response

plan_offers
object[]
required
Array of plan offer list items.
count
number
required
Total matching records.
limit
number
required
Page size used.
offset
number
required
Result offset used.
Response example
{
  "plan_offers": [
    {
      "id": "po_123",
      "name": "Coffee Monthly Variant Offer",
      "status": "enabled",
      "is_enabled": true,
      "target": {
        "scope": "variant",
        "product_id": "prod_123",
        "product_title": "Coffee Subscription",
        "variant_id": "variant_123",
        "variant_title": "1 kg",
        "sku": "COFFEE-1KG"
      },
      "allowed_frequencies": [
        { "interval": "month", "value": 1, "label": "Every month" }
      ],
      "discounts": [
        { "interval": "month", "frequency_value": 1, "type": "percentage", "value": 10, "label": "10% off" }
      ],
      "rules_summary": "Min 1 cycles · Stacking allowed",
      "effective_config_summary": {
        "source_scope": "variant",
        "source_offer_id": "po_123",
        "allowed_frequencies": [{ "interval": "month", "value": 1, "label": "Every month" }],
        "discounts": [{ "interval": "month", "frequency_value": 1, "type": "percentage", "value": 10, "label": "10% off" }],
        "rules": {
          "minimum_cycles": 1,
          "trial_enabled": false,
          "trial_days": null,
          "stacking_policy": "allowed"
        }
      },
      "updated_at": "2026-03-29T12:00:00.000Z"
    }
  ],
  "count": 1,
  "limit": 20,
  "offset": 0
}

Errors

CodeErrorMeaning
400invalid_dataInvalid query parameter shape, unsupported query value, or unsupported sort field

GET /admin/subscription-offers/:id

Returns the full detail payload for a single plan offer.

Path parameters

id
string
required
Plan offer ID.

Response

Returns a plan_offer object with all list fields plus:
plan_offer
object
required
Response example
{
  "plan_offer": {
    "id": "po_123",
    "name": "Coffee Monthly Variant Offer",
    "status": "enabled",
    "is_enabled": true,
    "target": {
      "scope": "variant",
      "product_id": "prod_123",
      "product_title": "Coffee Subscription",
      "variant_id": "variant_123",
      "variant_title": "1 kg",
      "sku": "COFFEE-1KG"
    },
    "allowed_frequencies": [{ "interval": "month", "value": 1, "label": "Every month" }],
    "discounts": [
      { "interval": "month", "frequency_value": 1, "type": "percentage", "value": 10, "label": "10% off" }
    ],
    "rules_summary": "Min 1 cycles · Stacking allowed",
    "effective_config_summary": {
      "source_scope": "variant",
      "source_offer_id": "po_123",
      "allowed_frequencies": [{ "interval": "month", "value": 1, "label": "Every month" }],
      "discounts": [{ "interval": "month", "frequency_value": 1, "type": "percentage", "value": 10, "label": "10% off" }],
      "rules": {
        "minimum_cycles": 1,
        "trial_enabled": false,
        "trial_days": null,
        "stacking_policy": "allowed"
      }
    },
    "created_at": "2026-03-29T10:00:00.000Z",
    "updated_at": "2026-03-29T12:00:00.000Z",
    "rules": {
      "minimum_cycles": 1,
      "trial_enabled": false,
      "trial_days": null,
      "stacking_policy": "allowed"
    },
    "metadata": { "source": "admin" }
  }
}

Errors

CodeErrorMeaning
404not_foundPlan offer does not exist

POST /admin/subscription-offers

Creates a new plan offer, or updates an existing one for the same target (create-or-upsert semantics).

Body parameters

name
string
required
Offer name. Trimmed.
scope
string
required
Target scope. One of product or variant.
product_id
string
required
Target product ID.
variant_id
string
Target variant ID. Required for variant-scoped offers; must be omitted or null for product-scoped offers.
is_enabled
boolean
required
Whether the offer is active.
allowed_frequencies
object[]
required
Array of allowed cadences. Each entry requires interval (week, month, or year) and a positive integer value. Must contain at least one entry.
discounts
object[]
Per-frequency discount definitions. Each entry requires interval, frequency_value, type (percentage or fixed), and value. Optional.
rules
object
Rules object with minimum_cycles, trial_enabled, trial_days, and stacking_policy. Optional.
metadata
object
Arbitrary metadata. Optional.
Request example
{
  "name": "Coffee Monthly Variant Offer",
  "scope": "variant",
  "product_id": "prod_123",
  "variant_id": "variant_123",
  "is_enabled": true,
  "allowed_frequencies": [{ "interval": "month", "value": 1 }],
  "discounts": [{ "interval": "month", "frequency_value": 1, "type": "percentage", "value": 10 }],
  "rules": {
    "minimum_cycles": 1,
    "trial_enabled": false,
    "trial_days": null,
    "stacking_policy": "allowed"
  },
  "metadata": { "source": "admin" }
}

Errors

CodeErrorMeaning
400invalid_dataInvalid request shape
400invalid_dataProduct-scoped offer specifies variant_id, or variant-scoped offer omits it
400invalid_dataProduct does not exist, or variant does not belong to the product
400invalid_dataDuplicate or invalid frequency definitions
400invalid_dataDiscount defined for a frequency not in allowed_frequencies, invalid discount range, or invalid trial configuration
409conflictConflicting override configuration from an inconsistent persisted state

POST /admin/subscription-offers/:id

Updates an existing plan offer source record. At least one field must be provided.

Path parameters

id
string
required
Plan offer ID.

Body parameters

All fields are optional; omit any field you do not want to change.
name
string
Updated offer name.
is_enabled
boolean
Updated enabled state.
allowed_frequencies
object[]
Replacement list of allowed cadences.
discounts
object[]
Replacement list of per-frequency discounts.
rules
object
Updated rules object.
metadata
object
Updated metadata.
Request example
{
  "name": "Coffee Monthly Variant Offer Updated",
  "is_enabled": true,
  "allowed_frequencies": [
    { "interval": "month", "value": 2 },
    { "interval": "year", "value": 1 }
  ],
  "discounts": [
    { "interval": "month", "frequency_value": 2, "type": "percentage", "value": 12 }
  ],
  "rules": {
    "minimum_cycles": 2,
    "trial_enabled": true,
    "trial_days": 14,
    "stacking_policy": "disallow_subscription_discounts"
  },
  "metadata": { "revision": 2 }
}

Errors

CodeErrorMeaning
400invalid_dataEmpty body with no fields, invalid request shape, or invalid frequency/discount/rules configuration
404not_foundPlan offer does not exist

POST /admin/subscription-offers/:id/toggle

Enables or disables a plan offer without modifying any other fields.

Path parameters

id
string
required
Plan offer ID.

Body parameters

is_enabled
boolean
required
Target enabled state.
Request example
{
  "is_enabled": false
}

Errors

CodeErrorMeaning
400invalid_dataInvalid request shape
404not_foundPlan offer does not exist