All routes require an authenticated Medusa Admin user. Unauthenticated requests return
401.Domain values
| Domain | Supported values |
|---|---|
| Status | enabled, disabled |
| Scope | product, variant |
| Frequency interval | week, month, year |
| Discount type | percentage, fixed |
| Stacking policy | allowed, disallow_all, disallow_subscription_discounts |
GET /admin/subscription-offers
Returns the paginated plan offer list for Admin DataTable views.Query parameters
Number of results per page.
Zero-based result offset for pagination.
Free-text search across name and product/variant titles.
Field to sort by. Database-backed:
name, scope, is_enabled, created_at, updated_at. In-memory: status, product_title, variant_title.Sort direction. One of
asc or desc.Filter by enabled/disabled state.
Filter by target scope. One of
product or variant.Filter by product ID.
Filter by variant ID.
Filter by frequency interval. One of
week, month, or year.Minimum discount value filter.
Maximum discount value filter.
Response
Array of plan offer list items.
Total matching records.
Page size used.
Result offset used.
Response example
Errors
| Code | Error | Meaning |
|---|---|---|
400 | invalid_data | Invalid 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
Plan offer ID.
Response
Returns aplan_offer object with all list fields plus:
Response example
Errors
| Code | Error | Meaning |
|---|---|---|
404 | not_found | Plan 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
Offer name. Trimmed.
Target scope. One of
product or variant.Target product ID.
Target variant ID. Required for
variant-scoped offers; must be omitted or null for product-scoped offers.Whether the offer is active.
Array of allowed cadences. Each entry requires
interval (week, month, or year) and a positive integer value. Must contain at least one entry.Per-frequency discount definitions. Each entry requires
interval, frequency_value, type (percentage or fixed), and value. Optional.Rules object with
minimum_cycles, trial_enabled, trial_days, and stacking_policy. Optional.Arbitrary metadata. Optional.
Request example
Errors
| Code | Error | Meaning |
|---|---|---|
400 | invalid_data | Invalid request shape |
400 | invalid_data | Product-scoped offer specifies variant_id, or variant-scoped offer omits it |
400 | invalid_data | Product does not exist, or variant does not belong to the product |
400 | invalid_data | Duplicate or invalid frequency definitions |
400 | invalid_data | Discount defined for a frequency not in allowed_frequencies, invalid discount range, or invalid trial configuration |
409 | conflict | Conflicting 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
Plan offer ID.
Body parameters
All fields are optional; omit any field you do not want to change.Updated offer name.
Updated enabled state.
Replacement list of allowed cadences.
Replacement list of per-frequency discounts.
Updated rules object.
Updated metadata.
Request example
Errors
| Code | Error | Meaning |
|---|---|---|
400 | invalid_data | Empty body with no fields, invalid request shape, or invalid frequency/discount/rules configuration |
404 | not_found | Plan offer does not exist |
POST /admin/subscription-offers/:id/toggle
Enables or disables a plan offer without modifying any other fields.Path parameters
Plan offer ID.
Body parameters
Target enabled state.
Request example
Errors
| Code | Error | Meaning |
|---|---|---|
400 | invalid_data | Invalid request shape |
404 | not_found | Plan offer does not exist |