Update subscription products

A subscription can handle different type of updates: adding or removing items, and the most common, making an exchange of an item by another one. In any of these case what the API requires is to get the new full cart

Endpoint /api/public/v1/subscriptions/:subscription_id/update_products

Method POST

Swagger https://docs.sequra.com/update/reference/post_api-public-v1-subscriptions-id-update-products#/

Action availability

An update is only accepted while the corresponding subscription action is available. Availability is exposed on the subscription itself — see Checking Availability — and the API enforces it: if the action is not available the request is rejected with 422 and nothing is changed.

Conditioncode returned when not met
The subscription is activeinvalid — Subscription must be active
Its cart is confirmed, so no other change is pendinginvalid — Cart must be confirmed
The subscription is paid upsubscription_unpaid — Subscription must be paid up to change its products

The last one is the case of a subscriber in arrears (impago): while there are unpaid charges the products cannot be changed. Check available_actions.change_item before offering the operation rather than relying on the request being accepted.

Rejections use the standard error envelope:

{
  "errors": [
    {
      "code": "subscription_unpaid",
      "title": "Subscription must be paid up to change its products"
    }
  ]
}

Shopper confirmation

An update requires the subscriber's confirmation — it is not applied when the request returns.

seQura sends the confirmation SMS automatically. On a successful request the shopper receives an SMS containing a link to review and confirm the changes; you do not need to send anything yourself. The new cart stays unconfirmed until the shopper follows that link, and you have to listen for a webhook to get the confirmation of the operation.

Details worth planning for:

  • A 200 means the change was registered and the SMS dispatched — not that it took effect. Do not treat the change as applied.
  • Delivery is best effort: an SMS failure does not fail the request, so a 200 is not proof that the shopper received it.
  • In sandbox the automatic SMS is only delivered when the merchant has sandbox SMS enabled. Do not infer production behaviour from a sandbox test.
  • To notify the shopper again, or to notify by email, use POST /api/public/v1/subscriptions/:id/send_sms or POST /api/public/v1/subscriptions/:id/send_email.

While a change is awaiting confirmation the cart is not confirmed, so a further update is rejected with Cart must be confirmed until the shopper confirms or the change is cancelled.

Payload

The service will calculate the difference between items based on their item IDs. In this example, the first item doesn't change, so no more data is required. The second item is new. If the previous cart had another item, that item would be removed because its ID is no longer present.

{
  "cart_items": [
    {
      "id": "5025d4dc-7eb2-4686-9b11-cde4e1469f4d"
    },
    {
      "type": "material_subscription",
      "reference": "NEW-001",
      "name": "New Product",
      "price_with_tax": 30000,
      "quantity": 1,
      "subscription_price": 1200,
      "subscription_period": "24 months"
    }
  ]
}

Response

{
  "success": true,
  "message": "Productos de suscripción actualizados exitosamente"
}


Exchanges and Extensions

What is an Exchange?

An exchange is when a subscriber replaces one or more items in their active subscription with different items — for example, swapping glasses frames or lenses for a new model.

An exchange is only possible when:

  • The subscription is active and paid
  • The subscriber has remaining exchange quota (see below)

Both conditions are enforced by the API — see Action availability.

Exchange Quota

A subscriber accrues 1 exchange for every completed subscription year, counted from the activation anniversary. Those accrued allowances are cumulative and carry over: a subscriber who has not exchanged during two completed years may make two exchanges.

First-year exchanges are disabled by default. A per-merchant setting enables them, granting one extra allowance during year 1 — before any year has completed.

That first-year allowance is use it or lose it. Unlike the accrued yearly allowances it does not carry over: if year 1 passes without an exchange, it is forfeited and the subscriber is back to one allowance per completed year.

Maximum exchanges allowed in total, by the year the subscription is in:

ConfigurationYear 1Year 2Year 3
First year exchanges disabled (default)012
First year exchanges enabled, year-1 allowance used123
First year exchanges enabled, year-1 allowance unused112

Only confirmed exchanges count against the quota, and contact-lens exchanges are excluded.

The API exposes both a counter and a boolean:

  • available_exchanges — number of exchanges remaining in the current quota period
  • available_actions.change_item — true when an exchange can be performed right now (quota remaining + subscription active and paid + cart confirmed)

What is an Extension?

An extension is not a customer-facing opportunity — it is a condition that applies automatically when a subscription enters its last year.

Once the subscription crosses the extension_available_on date (12 months before the renewal date), any exchange performed by the subscriber must include an extension: the subscription duration is extended by 12 months as part of the exchange. This ensures the subscriber is never exchanging into a subscription that is about to expire.

When an exchange triggers an extension:

  • The subscription duration is extended by 12 months
  • The extension_available_on date is recalculated for the new period
  • The exchange quota resets for the new year

Checking Availability

GET /api/public/v1/subscriptions/:id

The subscription response includes:

available_actions.change_item

true when an exchange is currently available — quota not exceeded, subscription active and paid, cart confirmed.

available_actions.extend_period

true when the subscription has entered its last year (current date is past extension_available_on). When this is true, any exchange must be performed with extend_duration: true.

extension_available_on

The date from which the extension condition applies.

{
  "extension_available_on": "2025-12-01",
  "available_actions": {
    "change_item": true,
    "extend_period": true,
    ...
  }
}

Interpreting the Combinations

change_itemextend_periodMeaning
truefalseExchange available, standard exchange (no extension needed)
truetrueExchange available, subscription in last year — exchange must include extension
falsetrueQuota used up for this year, subscription in last year — no exchange possible until next year
falsefalseNo exchange available

Triggering an Exchange

Use the Update Products endpoint:

POST /api/public/v1/subscriptions/:id/update_products

Pass the desired final state of items. The API automatically determines what was added, removed, or exchanged.

Relevant parameters:

ParameterTypeDefaultDescription
apply_yearly_limitbooleanfalseIf true, the request fails with 422 when the yearly exchange quota is exceeded
extend_durationbooleanfalseIf true, the subscription is extended by 12 months as part of the exchange

apply_yearly_limit governs the quota check only. The active, cart-confirmed and paid-up conditions are always enforced and cannot be opted out of.

The value of those parameters is responsibility of merchant. It allows to not extend subscription, or to not reduce exchanges quota when fixing some previous

Standard Exchange (no extension)

When change_item is true and extend_period is false:

{
  "cart_items": [
    { "id": "existing-item-uuid" },
    {
      "type": "material_subscription",
      "reference": "NEW-FRAME-001",
      "name": "New Frame",
      "price_with_tax": 25000
    }
  ],
  "apply_yearly_limit": true
}

Exchange with Extension

When extend_period is true, pass extend_duration: true. The exchange will extend the subscription by 12 months:

{
  "cart_items": [
    { "id": "existing-item-uuid" },
    {
      "type": "material_subscription",
      "reference": "NEW-FRAME-001",
      "name": "New Frame",
      "price_with_tax": 25000
    }
  ],
  "apply_yearly_limit": true,
  "extend_duration": true
}

Did this page help you?