Skip to main content
This page lists developer-facing changes to the Subscriptions Admin API, the Storefront API, Shopify Flow and the storefront JavaScript events, newest first. Each entry is dated the day the change reached production.
  • Additive changes (new endpoints, new response fields, new optional parameters, new values of an existing field) don’t break a client that ignores what it doesn’t know. Parse JSON leniently and don’t treat enum fields as closed lists.
  • Entries tagged Action required may need a code change in your integration. Each one says who is affected and what to do.
  • The API reference is generated from the live API, so it always shows the current contract. The Admin API base URL is https://subscription-admin.appstle.com, and Storefront API paths start with /subscriptions/cp/api/.
Admin API
Pagination links
GET /api/external/v2/subscription-contract-details, GET /api/external/v2/subscription-contract-details/customers, GET /api/external/v2/subscription-billing-attempts/past-orders, GET /api/external/v2/subscription-billing-attempts/past-orders/report, GET /api/external/v2/activity-logs and GET /api/external/v2/customer-retention-activities return a Link header with the URLs of the first and last pages, and of the next and previous pages when they exist. Until now, those URLs started with http://, even when you called https://subscription-admin.appstle.com. Following one sent the request over plain HTTP and got a 301 redirect to the same URL on https://. They now start with https://.Who is affected: clients that follow Link URLs as they are. Their requests now go straight to HTTPS, without the redirect. If your code rewrites http:// to https:// before it follows a Link URL, you can drop that step; keeping it does no harm.Reference: Retrieve subscription contracts, Retrieve customers with subscriptions, Get past orders, Get past orders report with detailed filtering, Get/Search activity logs, Get customer retention activities.
Storefront APINew fields
Customer portal settings

preservePortalOrderNote

GET /subscriptions/cp/api/shop-infos-by-current-login returns preservePortalOrderNote (boolean).Reference: Get shop info for the currently signed-in shop.
Admin APIStorefront APINew fieldsBehavior change
Customer portal settings, free product rewards

Pickup and delivery visibility in portal settings, and no gift cards as free products

  • GET /api/external/v2/customer-portal-settings/{id} and GET /subscriptions/cp/api/customer-portal-settings/{id} return three new booleans: apdHideDelivery, apdHidePickup and apdHideShipping.
  • POST /api/external/v2/subscription-groups, and PUT for new plans, return 400 when a free product in appstleCycles[] is a gift card. The title is UserGeneratedError: followed by the product’s name and is a gift card. Shopify doesn't allow free gift cards on subscription orders, so choose a different free product.
Reference: Get customer portal settings.
Storefront APINew fields
Campaigns

Upgrade selection when redeeming a campaign offer

POST /subscriptions/cp/api/campaigns/{id}/redeem accepts two new optional body fields, upgradeLineId (string) and upgradeSelection (object).Reference: Redeem a campaign offer.
Admin APIStorefront APINew fieldsBehavior change
Selling plans

repeatEvery on cycle rewards, and plan name translations

  • Each entry of appstleCycles[] carries a new field, repeatEvery (integer). It applies to repeating rewards only: they come back every this many orders, counting the first order. A free gift on order 5 (afterCycle 4) with repeatEvery 5 is given on orders 5, 10, 15 and so on. When it’s absent, repeating rewards come back every N orders, where N is the highest afterCycle among them. It’s ignored on pay-as-you-go prepaid plans.
  • repeatEvery is in the responses of GET /api/external/v2/subscription-groups (and its {id} variant), GET /api/external/v2/subscription-groups/all-selling-plans, GET /api/external/v2/subscription-contract-details/billing-interval, GET /subscriptions/cp/api/data/v2/products-selling-plans and GET /subscriptions/cp/api/subscription-groups/all-selling-plans. It’s accepted in POST /api/external/v2/subscription-groups, in new plans (without idNew) in PUT /api/external/v2/subscription-groups, and in lines[].pricingPolicy[] of POST /api/external/v2/subscription-contract-details/create-subscription-contract. PUT ignores appstleCycles[] on plans that already exist.
  • On the subscription group calls, a repeatEvery that isn’t a whole number from 1 to 150 returns 400 with the title UserGeneratedError:Repeat every has to be a whole number of orders from 1 to 150. Repeating rewards in one plan share one repeatEvery, so different values return 400 with the title UserGeneratedError:The repeating rewards repeat together, so they need the same repeat every. Reload the page and try again. A value that would start two repeating discount or shipping rewards on the same order returns 400 with a title that starts UserGeneratedError:With repeat every.
  • GET /subscriptions/cp/api/data/v2/products-selling-plans returns frequencyNameTranslations for plans whose translations are saved by plan ID. Until now it looked translations up by plan name only, so those plans came back without them. Plans whose translations are saved by name still return theirs.
Reference: Get all selling plans across all subscription groups.
Admin APIStorefront APINew fieldsBehavior change
Selling plans, product swaps

singleOrder on cycle rewards, and stopSwapEmails on swaps

  • Each entry of appstleCycles[] carries a new field, singleOrder (boolean). It applies to SHIPPING rewards only: the shipping price covers that reward’s own order, then the subscription goes back to its normal shipping price. It’s ignored on other reward types and on repeating rewards. The endpoints that return and accept repeatEvery, listed under October 26, return and accept it too.
  • POST /api/external/v2/subscription-contract-details/replace-variants-v3 now honors stopSwapEmails. With true, the swap sends no email notifications. Until now the endpoint ignored the field and always sent them.
Reference: Get all selling plans across all subscription groups, Replace product variants in a subscription contract.
Admin APIStorefront APINew fields
Customer portal settings

upsellOrderForAllProducts

GET /api/external/v2/customer-portal-settings/{id} and GET /subscriptions/cp/api/customer-portal-settings/{id} return upsellOrderForAllProducts (boolean).Reference: Get customer portal settings.
Storefront APINew enum valueBehavior changeAction required
Cancellation flows

Free Gift offers in cancellation flows

  • A cancellation flow can now offer a Free Gift. GET /subscriptions/cp/api/cancellation-flows/published?contractId={contractId} returns the flow in configJSON, a JSON string. In it, a TREATMENT_SET step’s payload, or that payload’s fallback, can have action FREE_GIFT, with freeGiftVariantId and, when the merchant set one, freeGiftTitle. Before this date, a flow with a Free Gift offer was rejected when the merchant saved it, so no flow returned one.
  • When the reason chosen through flowId and flowReasonId carries a Free Gift offer, POST /subscriptions/cp/api/cancellation-retention-offers/execute runs it the way it does for a reason-based offer (see October 16): retentionAction 9, the gift variant added to the next order as a free one-time item, once per subscription.
Who is affected: storefront code that renders a published cancellation flow and maps action to a fixed list of values.Action required: accept action values you don’t recognize.Reference: Execute a configured cancellation retention offer and record the retention activity.
Shopify FlowNew fields
Subscription Cancelled trigger

Cancellation note in the Subscription Cancelled trigger

The Subscription Cancelled trigger has a new field, Cancellation Note (string). It holds the note the customer or merchant wrote when the subscription was cancelled, or an empty string when there’s none. Only this trigger has it. Use it in a condition, or pass it to an action, for example to send the note to your support tool.Reference: Subscription Cancelled properties.
Admin APIStorefront APIAuthenticationBehavior change
Activity logs, shipping countries

Activity logs accept the X-API-Key header, and the countries list covers every shipping zone

  • GET /api/external/v2/activity-logs now accepts the API key in the X-API-Key header, like the other Admin API endpoints. Until now, a request that sent the key only in the header returned 400, because the api_key query parameter was required. api_key still works but is deprecated.
  • On a store that doesn’t use Shopify Markets shipping, GET /subscriptions/cp/api/v2/data/countries now returns the countries of every shipping zone in the store’s delivery profiles. Until now, countries in shipping zones past the first 50 were missing from the list.
Reference: Search activity logs, List supported shipping countries.
Admin APIStorefront APINew fields
Bundle settings, customer portal settings

Bundle display settings and freeze setting

  • GET /api/external/v2/subscription-bundle-settings/{id} and GET /subscriptions/cp/api/subscription-bundle-settings/{id} return hideProductTags and showFullProductDescription (booleans).
  • Customer portal settings return allowAddProductWhenContractIsFreeze (boolean). When it’s true, PUT /subscriptions/cp/api/v2/subscription-contracts-add-line-item and PUT /subscriptions/cp/api/v2/subscription-contracts-add-bundle-line-item add products even while the store locks changes until a subscription reaches its minimum number of orders.
Reference: Get subscription bundle settings by ID.
Admin APIStorefront APIJavaScript eventsNew enum valueAction required
Build-a-Box events, Free Gift offers

Build-a-Box discount cleanup event, and a Free Gift retention offer

  • The storefront script dispatches AppstleSubscription:Cart:BuildABoxDiscountRemoved on document only, not on window, when it removes a Build-a-Box discount code from a cart that has no box items. event.detail.codes lists the removed codes.
  • GET /api/external/v2/cancellation-managements/{id} and GET /subscriptions/cp/api/cancellation-managements/{id} can return a cancellation reason, or a reason’s fallbackOffer, with cancellationAction 9 (Free Gift), freeGiftVariantId and, when the merchant set one, freeGiftTitle. The reasons are in cancellationReasonsJSON, a JSON string.
  • POST /subscriptions/cp/api/cancellation-retention-offers/execute can run a Free Gift offer. The response then carries retentionAction 9 and retentionActionLabel Free Gift, and retentionActivity.additionalInfo (a JSON string) holds freeGiftVariantId and, when the merchant set one, freeGiftTitle. The call adds the merchant’s gift variant to the subscription’s next order as a free one-time item. A subscription gets the gift once.
Who is affected: clients that map retentionAction or cancellationAction to a fixed list of values, and storefront code that runs cancellation offers.Action required: accept retentionAction and cancellationAction values you don’t recognize. When a Free Gift offer returns 400, show the title to the customer:
  • UserGeneratedError:A free gift has already been added to this subscription. The subscription already got its gift.
  • UserGeneratedError:Your free gift is already being added. Please wait a moment. An earlier request is still running.
  • UserGeneratedError:Another billing operation is already in progress. Please wait until all ongoing processes are completed. An order is being placed. Try again once it’s done.
  • UserGeneratedError:This free gift offer has no gift product. The merchant hasn’t set a gift product.
  • UserGeneratedError: followed by the merchant’s own message, when the store locks changes until a subscription reaches a minimum number of orders.
Reference: JavaScript events, Get cancellation management configuration, Execute a configured cancellation retention offer and record the retention activity.
Admin APINew endpoint
Delivery price

Keep a custom delivery price for one subscription

New PUT /api/external/v2/subscription-contracts-update-delivery-price-override takes contractId and allowDeliveryPriceOverride as query parameters. true keeps the contract’s current delivery price and stops automatic shipping price syncs for it: pair it with PUT /api/external/v2/subscription-contracts-update-delivery-price to keep a price you set. false syncs the delivery price with the store’s Shopify shipping settings again and recalculates it in the background.Reference: Turn custom delivery price on or off for a subscription contract.
Admin APIStorefront APINew fields
Customer portal settings

settingsVersion and expectedShopCustomizationData

Customer portal settings return settingsVersion (string) and expectedShopCustomizationData[] (id, labelId, value).Reference: Get customer portal settings.
Admin APIStorefront APIShopify FlowNew endpointNew fieldsNew enum valueAction required
Gift subscriptions, immediate billing

Gift subscriptions and schedule modes for immediate orders

  • Selling plans return giftSendMaxDays (integer), and POST and PUT /api/external/v2/subscription-groups accept it in subscriptionPlans[].
  • New GET /subscriptions/cp/api/subscription-gifts?contractIds=… returns the gift details of the listed contracts that belong to the signed-in customer, keyed by contract ID. Without contractIds, it returns an empty object.
  • eventType in GET /api/external/v2/activity-logs has a new value, SEND_GIFT_PURCHASED_MERCHANT_EMAIL.
  • PUT /subscriptions/cp/api/subscription-billing-attempts/attempt-billing/{id} accepts an optional scheduleMode query parameter that sets how ordering now affects the schedule. extra_order_only places an extra order and leaves the upcoming orders as they are. extra_order_recalculate places an extra order and recalculates the upcoming order dates. Without it, the store’s Manual Order Processing setting applies.
  • Send scheduleMode only when customer portal settings return allowOrderNowScheduleChoice as true. Otherwise the call returns 400 with the title UserGeneratedError:This schedule option is no longer available. Please refresh the page and try again. An unrecognized value returns 400 with the title UserGeneratedError:Invalid schedule option. Please refresh the page and try again.
  • Customer portal settings return allowOrderNowScheduleChoice and hideRescheduleTimeInExtension (booleans).
  • POST and PUT /api/external/v2/subscription-groups return 400 when a plan has gift true and a giftSendMaxDays outside 1 to 365, with the title UserGeneratedError:Gift invitations can be scheduled between 1 and 365 days ahead. and the errorKey invalidGiftSendMaxDays, or a minQuantity above 1, with the title UserGeneratedError:Gifts must be purchased one at a time. Leave minimum quantity empty or set it to 1. and the errorKey invalidGiftMinimumQuantity.
  • PUT /api/external/v2/subscription-contracts-update-max-cycles returns 400 with the title UserGeneratedError:Gift subscriptions end after their prepaid deliveries, so their maximum can't be changed. and the errorKey giftMaxCyclesChangeBlocked when the subscription is a gift subscription. The Shopify Flow action Update Max Cycles fails on gift subscriptions for the same reason.
Who is affected: clients that map eventType to a fixed list of values.Action required: accept values you don’t recognize.Reference: Get subscription gifts, Create a new subscription group, Update subscription group details, Update maximum cycles for a subscription contract.
Admin APIStorefront APINew fieldsNew enum valueBehavior changeAction required
Activity logs, gift subscriptions, charge cap

Gift email event types and gift selling plans

  • eventType in GET /api/external/v2/activity-logs has two new values: SEND_GIFT_RECEIVED_EMAIL and SEND_GIFT_THANK_YOU_EMAIL.
  • Selling plans return gift (boolean), and POST and PUT /api/external/v2/subscription-groups accept it in subscriptionPlans[].
  • GET /subscriptions/cp/api/data/v2/products-selling-plans accepts a contractId query parameter.
  • POST /api/external/v2/subscription-groups, and PUT for new plans, return 400 with the title UserGeneratedError:Remove rewards before selling this plan as a gift. and the errorKey giftRewardsNotSupported when a plan has gift true and rewards in appstleCycles[].
  • The same calls return 400 with the title UserGeneratedError:Each new gift option needs its own name. and the errorKey giftFrequencyNameNotUnique when a new plan with gift true has the same frequencyName as another plan in the request, ignoring case, and with the title UserGeneratedError:Gift plans must use Prepaid One-Time with one billing cycle. and the errorKey invalidGiftPlan when a plan with gift true has a planType other than PREPAID or a maxCycles other than 1.
Who is affected: clients that map eventType to a fixed list of values.Action required: accept values you don’t recognize.

The charge cap’s 500 USD limit is converted into the subscription’s currency

  • The charge cap limits a subscription that has no original order amount on file to 500 USD. That limit is now converted into the subscription’s currency. Until now, the number 500 was compared with the subscription’s amount in whatever currency it was in.
  • The same limit applies when you raise the price of a line item that costs 0, with PUT /api/external/v2/subscription-contracts-update-line-item-price or PUT /api/external/v2/subscription-contracts-update-line-item-pricing-policy. It also applies when you trigger billing with PUT /api/external/v2/subscription-billing-attempts/attempt-billing/{id} for a subscription with no original order amount on file.
  • The 400 messages for this limit now show it with its currency code, for example 500.00 USD. Until now they showed only 500.
  • When Appstle has no exchange rate for the subscription’s currency, these calls return 400 with the title UserGeneratedError:Unable to verify this subscription's safety limit. Please try again or contact Appstle support. and the errorKey charge_cap_currency_unavailable.
Who is affected: Admin API clients that change products, line item prices or billing on subscriptions in a currency other than USD.Action required: after a charge_cap_currency_unavailable error, send the request again later. If it keeps failing, contact support@appstle.com.Reference: Trigger immediate billing for an order.
Storefront APIBehavior changeAction required
Cancellation, pausing

Cancellation rules apply during free trials, and pauses have a limit

DELETE /subscriptions/cp/api/subscription-contracts/{id} now applies the store’s customer cancellation rules to subscriptions in a free trial too. Until now, a subscription in a free trial skipped them.
  • If the merchant doesn’t let customers cancel, the call returns 400 with the title UserGeneratedError:Permission denied, Subscription cannot be modified.
  • If the merchant limits cancellation by how close the next billing date is, the call returns 400 with the errorKey timeBasedRestriction. Its title is UserGeneratedError: followed by a message that can be the merchant’s own text, so match the errorKey, not the title.
PUT /subscriptions/cp/api/subscription-contracts-update-status with status PAUSED and a pauseDurationCycle now works out the date the pause ends from the subscription’s billing schedule. It returns 400 with the title Scheduled pauses must end within 364 days. Please choose a shorter pause duration. and the errorKey pauseDurationTooLong when that date is more than 364 days away. It returns 400 with the errorKey invalidPauseSchedule when the date can’t be worked out or isn’t in the future.Until now, a pauseDurationCycle at least as large as the number of upcoming orders paused the subscription with no end date. A pause offer run through POST /subscriptions/cp/api/cancellation-retention-offers/execute follows both rules, and the free trial rule, with the same 400s.PUT /subscriptions/cp/api/subscription-contracts-update-status with status PAUSED or CANCELLED also returns 400 while the subscription is in a free trial and the customer portal settings have allowCancelDuringFreeTrial set to false. The title is UserGeneratedError:Pausing is not allowed during free trial or UserGeneratedError:Cancellation is not allowed during free trial, and the errorKey is pause_not_allowed_during_trial or cancel_not_allowed_during_trial. When the call can’t tell whether the subscription is in a free trial, it returns 400 with the title UserGeneratedError:Unable to verify the subscription's free trial. Please try again. and the errorKey free_trial_status_unavailable.Who is affected: storefront code that lets customers cancel or pause a subscription.Action required: handle a 400 from this call for subscriptions in a free trial, as you already do for other subscriptions. Don’t offer a pause that ends more than 364 days from now. Read allowCancelDuringFreeTrial in the customer portal settings, and don’t offer pause or cancel during a free trial when it’s false. Show the 400 title when a call is refused.Reference: Cancel and delete a subscription contract, Update subscription contract status.
Admin APIStorefront APINew fields
Customer portal settings

allowOrderNowOnListPage

Customer portal settings return allowOrderNowOnListPage (boolean).
Storefront APINew fields
Live order status, rescheduling

Pickup data on live order status, and a billing-date guard

  • GET /subscriptions/cp/api/orders/{orderId}/live-status accepts includePickupDeliveryData (boolean) and then returns pickupDeliveryData (string).
  • PUT /subscriptions/cp/api/subscription-billing-attempts/skip-order/{id} and PUT /subscriptions/cp/api/subscription-contracts-update-billing-date accept an optional expectedBillingDate query parameter, and update-billing-date also accepts expectedBillingAttemptId. Send the upcoming order’s billingDate exactly as the API returned it. For update-billing-date, also send that order’s billing attempt id. If the upcoming order has changed since, the call changes nothing and returns 400 with the title UserGeneratedError:This upcoming order has changed. Review the latest order before trying again.
Reference: Get the live fulfillment status for an order.
Admin APIStorefront APINew fields
Customer portal settings

showSwapOnlyForOutOfStockBABProducts

Customer portal settings return showSwapOnlyForOutOfStockBABProducts (boolean).
Storefront APIBehavior changeAction required
Minimum quantity

Customers can’t go below a plan’s minimum quantity

When a customer lowers a line’s quantity with PUT /subscriptions/cp/api/subscription-contracts-update-line-item-quantity, or swaps a line with POST /subscriptions/cp/api/subscription-contract-details/replace-variants-v2 or replace-variants-v3, the call returns 400 with the title UserGeneratedError:This subscription plan requires at least {minQuantity} of this product. and the errorKey minQuantityNotReached if the new quantity is below the selling plan’s minQuantity. A line already below the minimum can stay at its current quantity. Admin API calls aren’t affected.Who is affected: storefront code that lets customers change quantities or swap products.Action required: don’t offer quantities below the plan’s minQuantity, and show the 400 title when a call is refused.Reference: Update line item quantity on a subscription contract, Replace contract variants in bulk (v2), Replace product variants in a subscription contract.
Admin APIStorefront APIBehavior changeAction required
Delivery address

Refused delivery addresses say what’s wrong

When Shopify refuses the address itself (its message starts with Delivery method or Validation failed), PUT /api/external/v2/subscription-contracts-update-shipping-address and PUT /subscriptions/cp/api/subscription-contracts-update-shipping-address now return 400 with the errorKey addressUpdateFailed and a title that names the problem. For a missing or invalid phone number, ZIP or postal code, city, last name or state, the title is Appstle’s wording, for example UserGeneratedError:Add a phone number to this address. This store requires one for shipping. or UserGeneratedError:Enter a ZIP / postal code. For other address problems, it’s UserGeneratedError: followed by Shopify’s message. Every other refusal keeps the generic title described under September 17.Who is affected: code that shows or matches on the title of a refused address update.Action required: show the title after UserGeneratedError: and don’t match on its wording.Reference: Update subscription delivery address and method, Update subscription delivery address and method (Storefront API).
Storefront APIPartner FrameworkBehavior changeAction required
Cancellation offers, order notes, line items, partner approvals

Skip offers, order notes and line item edits follow the store’s settings

  • POST /subscriptions/cp/api/cancellation-retention-offers/execute skips the number of orders the merchant set on the cancellation reason when it runs a skip offer. skipCycle in the request counts only when the shop’s cancellation management setting dynamicallyPauseCycleOnCancellation is on and the request has no flowId or flowReasonId. It must then be between 1 and the configured number, or the call returns 400 with the title UserGeneratedError:Invalid skip duration for this retention offer. Otherwise the call ignores skipCycle. Until now, any skipCycle replaced the configured number.
  • PUT /subscriptions/cp/api/subscription-contract-details returns 400 with the title UserGeneratedError:Permission denied, Order notes are not allowed when a customer changes the order note and the merchant has turned order notes off for the customer portal. Until now, the call saved the note.
  • PUT /subscriptions/cp/api/subscription-contracts-update-line-item-attributes and PUT /subscriptions/cp/api/subscription-contracts-edit-line-item-attributes keep the attributes Appstle sets on a line, such as _appstle-bb-id and _min-cycles. The call ignores those keys in the request, except a Build-a-Box line’s product selection, and keeps the line’s existing values. Changing any other attribute needs the merchant’s permission to edit attributes, or the call returns 400 with the title UserGeneratedError:Permission denied, Editing attributes is not allowed. A request that repeats a key returns 400 with the title UserGeneratedError:Duplicate custom attribute key.
  • PUT /subscriptions/cp/api/update-classic-bab-subscription/{contractId} checks each line it changes. It returns 400 with the title UserGeneratedError:This line is not a Build-A-Box product. for a line outside the box, UserGeneratedError:Permission denied, Editing or removing this product is not allowed for a product the merchant has locked, and UserGeneratedError:Permission denied, Removing products from subscription is not allowed for a quantity of 0 when customers can’t remove products. A line that must stay for a minimum number of orders returns 400 until it has, with a title that is UserGeneratedError: followed by the merchant’s message.
  • When a customer swaps the product on a Build-a-Box line, the new line keeps all of the old line’s attributes, so it stays in the box. Until now it kept only Appstle’s own attributes unless the store keeps line attributes on swaps.
Who is affected: clients that send skipCycle to a skip offer, and customer portals that let customers edit the order note, line item attributes or Build-a-Box quantities.Action required: send a skipCycle no larger than the number the offer shows, send only your own attribute keys, and show the 400 title to the customer when a call is refused.Reference: Execute a configured cancellation retention offer and record the retention activity, Update subscription contract details, Edit custom attributes on a single line item, Update line item quantities for a classic Build-a-Box subscription.

Partner approval callbacks and nonce expiry

  • When a merchant approves a connection and Appstle sends the partner an apst_ access token, Appstle now saves the token before it posts it to the partner’s approval callback, /appstle/approved by default. A call made with the token as soon as it arrives no longer returns 401.
  • Any 2xx reply from the callback now counts as received, whatever its body. Until now, a 2xx reply whose body wasn’t a JSON object failed the approval.
  • When the callback doesn’t answer with a 2xx status within 5 seconds, the approval fails: Appstle deletes the token it created, leaves the connection pending, and returns 400 with the errorKey PARTNER_UNREACHABLE to the merchant. The partner gets no usable token from that attempt. See What if the callback fails?.
  • POST /api/partner/{partnerId}/verify now returns { "verified": false } for a callback_nonce that is more than 5 minutes old, as the partner guide already said. Until now, an expired nonce could still be verified.
Who is affected: partner apps that receive the approval callback or call the verify endpoint.Action required: make the approval callback answer with a 2xx status within 5 seconds, call verify within 5 minutes of receiving the nonce, and ask the merchant to approve again after a failed approval.
Storefront APINew fieldsBehavior change
Cancellation offers, merging subscriptions

Offer tier on cancellation discounts, and no merging of cancelled subscriptions

  • PUT /subscriptions/cp/api/subscription-contracts-cancellation-discount accepts an optional offerTier query parameter, PRIMARY or FALLBACK, that records which cancellation offer the customer accepted. It’s saved in the retention activity’s additionalInfo.offerTier.
  • POST /subscriptions/cp/api/subscription-contract-details/merge-contract returns 400 with the title UserGeneratedError:Cancelled subscriptions cannot be merged. Please refresh the page and try again. when either subscription is cancelled. Until now the call went ahead.
Reference: Merge an existing contract into another.
Admin APIStorefront APIAuthenticationNew fieldsAction required
Authentication, free products

Legacy API key revocation, and free products kept at quantity 1

  • Under More → API Access in the Appstle admin, the Legacy API Key now has a Revoke button. After a merchant revokes it, every request that sends that key returns 401, with the same body as any other wrong key (see Authentication errors). Appstle doesn’t issue a replacement. Keys that start with apst_ aren’t affected.
  • Each appstleCycles[] entry returns restrictFreeProductQuantity (boolean). When true, each free product stays at quantity 1 regardless of the parent line item’s quantity. It’s returned and accepted wherever repeatEvery is (see October 26).
Who is affected: integrations that still send the key shown on the Legacy API Key card.Action required: create an apst_ key under More → API Access and move your integration to it before the merchant revokes the legacy key.Reference: Managing keys.
Admin APIBehavior changeAction required
Subscription groups

Plans need their own names, and rewards sent with new plans are saved

  • POST /api/external/v2/subscription-groups and PUT /api/external/v2/subscription-groups return 400 with the title UserGeneratedError:Each subscription option needs its own name. Rename one of the duplicate options. when two plans in the request have the same frequencyName. The comparison ignores spaces and letter case. On PUT, the call refuses a duplicate only when at least one of the two plans is new, meaning it has no idNew. Until now, both calls saved duplicate names.
  • appstleCycles[] sent with a plan in POST /api/external/v2/subscription-groups, or with a new plan in PUT /api/external/v2/subscription-groups, is checked and saved as that plan’s rewards. Until now both calls dropped it. PUT still ignores appstleCycles[] on plans that already exist. A reward the store can’t use returns 400, for example UserGeneratedError:Your current plan doesn't include subscription rewards. Upgrade your plan to add or change rewards. or UserGeneratedError:Select the free product for every free-gift reward before saving.
Who is affected: integrations that create subscription groups or add plans with names that repeat, or with appstleCycles[] in the body, for example by sending back a group they read.Action required: give every plan in a group its own frequencyName, and send appstleCycles[] only with the rewards you want a new plan to have. Show the 400 title when a call is refused.Reference: Create a new subscription group (selling plan group), Update subscription group details and product assignments.
Admin APIStorefront APIBehavior changeNew fieldsAction required
Immediate billing, locations

Trigger immediate billing returns an acknowledgement

  • PUT /api/external/v2/subscription-billing-attempts/attempt-billing/{id} used to return 200 with an empty body. It now returns 200 with billingAttemptId, requestedBillingAttemptId, contractId, billingDate, status and message. This confirms the attempt was queued, not that the payment succeeded: the charge is sent to Shopify right after the call, and the result comes later.
  • billingAttemptId is the attempt that was queued, and it can differ from the id you sent (returned as requestedBillingAttemptId). status is the attempt’s status when it was queued, for example IMMEDIATE_TRIGGERED.
  • When no billing attempt could be triggered, the call returns 400 with the title UserGeneratedError:Billing attempt could not be triggered.
  • GET /subscriptions/cp/api/delivery-profiles/get-locations returns fulfillsOnlineOrders (boolean) for each location.
Who is affected: integrations that treat a 200 from attempt-billing as a successful payment, or that expect an empty body.Action required: get the payment result from the subscription.billing-success and subscription.billing-failure webhooks. Or poll GET /api/external/v2/subscription-billing-attempts/past-orders?contractId={contractId} until the attempt with the returned billingAttemptId has status SUCCESS or FAILURE. SECURITY_CHALLENGE means the card issuer asked for 3D Secure, and no webhook is sent for it.Reference: Trigger immediate billing for an order, Get past orders.
Admin APIStorefront APINew fieldsBehavior changeAction required
Shipping

Delivery type, shipping model and Markets shipping options

  • GET /subscriptions/cp/api/data/contract-live-shipping-rates returns deliveryType for each rate.
  • GET /subscriptions/cp/api/v2/data/countries accepts an includeShippingModel query parameter.
  • deliveryMethodId is optional on PUT /api/external/v2/bulk-automations/delivery-method.
  • On a store that uses Shopify Markets shipping, GET /subscriptions/cp/api/data/shop-delivery-options first returns one item per market that has active shipping options, as { name, marketOptions }, where marketOptions lists each option as { id, name }. The app’s delivery profiles follow, in the same shape as before.
  • When the delivery options can’t be loaded, shop-delivery-options now fails with a server error instead of returning an empty list.
  • On a store that uses Shopify Markets shipping, PUT /api/external/v2/subscription-contracts-update-delivery-method and PUT /subscriptions/cp/api/subscription-contracts-update-delivery-method return 400 with the title UserGeneratedError:The selected shipping option is no longer available. and the errorKey shippingOptionUnavailable when delivery-method-id names a shipping option that no longer exists.
Who is affected: storefront code that reads every shop-delivery-options item as a delivery profile.Action required: check each item for marketOptions before you read it as a delivery profile, and handle an error response from shop-delivery-options.Reference: Bulk update subscription delivery method, List delivery options available for the shop, Update subscription delivery method, Update delivery method on a subscription contract.
Admin APIStorefront APINew fields
Bundle settings

stickyCheckoutButton

Bundle settings return stickyCheckoutButton (boolean).
Admin APIStorefront APIBehavior change
Unskipping an order, splitting

A clear error when an order can’t be unskipped yet, and one-time products in splits

PUT /api/external/v2/subscription-billing-attempts/unskip-order/{id} and PUT /subscriptions/cp/api/subscription-billing-attempts/unskip-order/{id} return 400 with the title UserGeneratedError:This order is currently being processed by a billing job. Please wait a few minutes and try again. and the errorKey concurrencyFailure while a billing job is working on the subscription. Until now the call failed with a 409 or 500 and no usable message. Wait a few minutes and send the request again.The split calls described under September 19 no longer refuse one-time products. The invalidSplitProducts refusal now has the title UserGeneratedError:Select products to move. Free gifts cannot be selected.Reference: Unskip a previously skipped order, Split or duplicate an existing subscription contract.
Admin APIStorefront APIBehavior changeAction required
Splitting a subscription

Splits check the products you choose

POST /api/external/v2/subscription-contract-details/split-existing-contract and POST /subscriptions/cp/api/subscription-contract-details/split-existing-contract check the line IDs in the request body before they split or duplicate a subscription. Each refusal is a 400:
  • The title is UserGeneratedError:Select subscription products to move. Free gifts and one-time products cannot be selected. and the errorKey is invalidSplitProducts when the list is empty, names a line that isn’t in the subscription, or names a free gift or a one-time product.
  • With attemptBilling true, the title is UserGeneratedError:The selected products are skipped for the next order. Unskip a product before ordering it now, or split without placing an order. and the errorKey is splitProductsSkipped when every selected product is skipped for the next order.
  • With isSplitContract true, the title is UserGeneratedError:At least one subscription product must remain in the current subscription. Free gifts and one-time products do not count. when the split would leave no subscription product in the original subscription. Until now the call refused only when the list named every line, with the title UserGeneratedError:All products cannot be selected, at least one product is required in the current contract.
  • With isSplitContract true, the rules for removing a product, such as the store’s minimum number of products and a product’s minimum number of orders, are checked before the new subscription is created. Until now a refusal came after it had been created. If the products then can’t be removed from the original subscription, the call returns 400 with the title UserGeneratedError:The products could not be removed from the original subscription. No immediate order was requested. Contact support before trying again. and the errorKey splitRemovalFailed. With attemptBilling true, the new subscription’s order is placed only after the products are removed.
Who is affected: code that splits or duplicates subscriptions through either API.Action required: offer only subscription products in a split, and show the 400 title when a call is refused.Reference: Split or duplicate an existing subscription contract, Split or duplicate an existing subscription contract (Storefront API).
Admin APIStorefront APIPartner FrameworkBehavior changeAction required
Error responses

Error responses use appstle.com problem types and hide internal messages

The application/problem+json error responses changed in two ways:
  • The type of an error now starts with https://appstle.com/problem/, for example https://appstle.com/problem/problem-with-message. Before, it started with a different base URL.
  • detail no longer carries internal error text. A server error (5xx) has the detail An unexpected error occurred. Please try again later. A 400 for a path or query parameter of the wrong type, such as a customer portal settings ID that isn’t a number, has the detail Unexpected runtime exception. A 400 for a request body that isn’t valid JSON has the detail Unable to convert http message.
Who is affected: clients that match on the type URL, or on the text of detail in these errors.Action required: stop matching on type. Branch on status, and on errorKey or title where the body has them. Treat detail in these errors as a generic message.

Delivery address updates return specific errors

PUT /api/external/v2/subscription-contracts-update-shipping-address and PUT /subscriptions/cp/api/subscription-contracts-update-shipping-address now return 400 with these titles and errorKeys:
  • invalidCountry: UserGeneratedError:Choose a valid country for this address. when countryCode isn’t a valid country code.
  • addressRequired: UserGeneratedError:Enter a street address. when address1 is blank.
  • locationRequired: UserGeneratedError:Choose a pickup location. when the delivery method is local pickup and locationId is blank.
  • addressUpdateFailed: UserGeneratedError:We couldn't update this delivery address. Check the address details and try again. when Shopify refuses the change. Until now, the title was Shopify’s own message. From October 4, a refusal of the address itself names the problem again.
Who is affected: code that updates a delivery address and shows or matches on the error message.Action required: check the country, street address and pickup location before you call, and show the title after UserGeneratedError: when a call is refused.Reference: Update subscription delivery address and method, Update subscription delivery address and method (Storefront API).
Admin APIStorefront APINew enum valueNew fieldsAction required
Activity logs, live order status

New activity log entity type and line item selling plan name

  • entityType in GET /api/external/v2/activity-logs has a new value, QUICK_ACTION_LINK.
  • GET /subscriptions/cp/api/orders/{orderId}/live-status returns lineItems[].sellingPlanName.
Who is affected: clients that map entityType to a fixed list of values.Action required: accept values you don’t recognize.
Admin APIStorefront APIBehavior changeAction required
Request parameters and bodies

Requests can’t name another customer or store

  • A Storefront API request that names a customerId other than the signed-in customer’s returns 403 with the detail Customer does not match the authenticated session. This covers customerId and customer_id in the query string (for example GET /subscriptions/cp/api/loyalty-integration/earn-options?customerId=...) and in the request body (for example PUT /subscriptions/cp/api/subscription-contract-details).
  • A request body whose shop, shopName, shopDomain or shop_domain field names another store returns 403 with the detail Shop does not match the authenticated session. This covers Storefront API request bodies, and Admin API request bodies that have one of these fields, such as PUT /api/external/v2/product-swaps. The same check on query parameters started on September 6.
Who is affected: storefront code that sends a customerId that isn’t the signed-in customer’s, or a shop field that isn’t the store’s own, and Admin API clients that send another store’s domain in one of these body fields.Action required: leave customerId and the shop fields out of requests, or send the signed-in customer’s and the store’s own values. The session and the API key already identify both.Reference: Get available ways to earn loyalty points, Update subscription contract details, Update an existing product swap automation.
Admin APIPartner FrameworkBehavior changeAction required
Partner Framework, Build-a-Box pages

One error for every failed partner authentication

The Partner Framework endpoints that partners call under /api/partner/{partnerId}/ now return one error when authentication fails. A Partner ID that is wrong or belongs to a deactivated partner returns 400 with the errorKey TOKEN_INVALID and the title UserGeneratedError:Invalid partner credentials., the same as a wrong secret, an invalid HMAC signature or an old timestamp. Until now, an unknown or deactivated Partner ID returned PARTNER_NOT_FOUND.Who is affected: partners whose code branches on PARTNER_NOT_FOUND.Action required: stop handling PARTNER_NOT_FOUND. On TOKEN_INVALID, check the Partner ID first, then the secret or HMAC key.Reference: Partner integration.

Build-a-Box pages clean the top and bottom HTML

Build-a-Box pages now clean the bundleTopHtml and bundleBottomHtml fields of POST and PUT /api/external/v2/build-a-box before they show them. The API still stores what you send. On the page, Appstle keeps standard formatting tags, links, images and tables with a fixed list of attributes, and style declarations from a fixed list of properties. It removes everything else, such as <script>, <style>, <iframe> and <form> tags and event handler attributes like onclick. It also drops any link or image URL whose scheme isn’t http or https, such as javascript:, except mailto: and tel: links. Relative URLs are kept.Who is affected: developers who set these fields with scripts, styles or other interactive HTML.Action required: put scripts and styles in your theme and use plain HTML in these fields.Reference: Create a new Build-a-Box subscription bundle.
Admin APIBehavior change
Subscription products, selling plans

removeDiscount is honored, and fixed date schedules allow 24 dates

  • PUT /api/external/v2/subscription-contracts-remove-line-item now honors removeDiscount=false and keeps the subscription’s discounts where it can, as the reference describes. Until now the endpoint ignored the parameter and always removed the discounts that applied only to the removed line. Without removeDiscount, or with true, nothing changes.
  • POST and PUT /api/external/v2/subscription-groups accept up to 24 entries in a plan’s fixedBillingDates, up from 12. A request with more than 24 returns 400 with the title Fixed date schedules support up to 24 billing dates a year. The title used to say up to 12.
Reference: Remove product from subscription, Create a new subscription group (selling plan group).
Admin APINew enum valueAction required
Activity logs

New activity log event type SUPPORT_APD_DATA_REMOVED

eventType in GET /api/external/v2/activity-logs has a new value, SUPPORT_APD_DATA_REMOVED.Who is affected: clients that map eventType to a fixed list of values.Action required: accept values you don’t recognize.
Admin APINew enum valueAction required
Activity logs

New activity log event type SUPPORT_APD_DATA_UPDATED

eventType in GET /api/external/v2/activity-logs has a new value, SUPPORT_APD_DATA_UPDATED.Who is affected: clients that map eventType to a fixed list of values.Action required: accept values you don’t recognize.
Admin APIStorefront APIBehavior changeAction required
Request parameters

Query parameters can’t name another store

A request whose shop, shopName, shopDomain or shop_domain query parameter names a store other than the one its API key or customer session belongs to returns 403 with the detail Shop does not match the authenticated session. This applies to the Admin API and the Storefront API.Who is affected: clients that send one of these parameters with another store’s domain.Action required: leave these parameters out, or send the store’s own domain.Reference: Get all subscription groups.
Admin APINew endpoint
Subscription groups

Add products to a subscription group in the background

These endpoints are for large batches (hundreds to thousands of IDs) that could time out on the synchronous endpoints.
  • New POST /api/external/v2/subscription-groups/v3/{id}/add-products takes the same body as POST /api/external/v2/subscription-groups/{id}/bulk-add-products (productIds and variantIds, at least one of them). It returns 202 right away with status processing and subscriptionGroupId, and adds the products in the background.
  • One job runs per store at a time. A second POST while a job is running returns 400 with the title UserGeneratedError: A bulk add-products job for this shop is already in progress. A POST for an unknown group ID returns 400 with the title Invalid id.
  • New GET /api/external/v2/subscription-groups/v3/{id}/add-products/status reports the store’s latest job: found, running, executionStatus (for example RUNNING, SUCCEEDED or FAILED), errorInfo, subscriptionGroupId and matchesRequestedGroup. Poll it until running is false. If matchesRequestedGroup is false, a newer job for another group has replaced yours, and the status isn’t about your request. A store that has never run a job gets only found: false, running: false and executionStatus: null, whatever the group ID.
Reference: Add products or variants to an existing subscription group (async), Check status of an async add-products job.
Admin APIStorefront APINew fields
Subscription contracts

Contract revisionId

Contract responses, for example GET /api/external/v2/subscription-contracts/contract-external/{contractId}, return revisionId.
Storefront APINew endpointNew fields
Shipping rates

Live carrier-quoted shipping rates

  • New GET /subscriptions/cp/api/data/contract-live-shipping-rates lists live carrier-quoted shipping rates for a subscription contract.
  • PUT /subscriptions/cp/api/subscription-contracts-update-delivery-method accepts a carrier-rate (boolean) query parameter, and GET /subscriptions/cp/api/shop-infos-by-current-login returns disableShippingPricingAutoCalculation and recalculateShippingPriceBeforeBilling.
Reference: List live carrier-quoted shipping rates for a subscription contract.
Storefront APINew endpointBehavior change
Freeze status, fulfillment

Read the freeze status of a contract, and reschedule a fulfillment shared by several subscriptions

New GET /subscriptions/cp/api/subscription-contracts-freeze-status-detail reads the freeze (skip-billing) status of a contract.When several subscriptions came from the same order, PUT /subscriptions/cp/api/subscription-contract-details/subscription-fulfillment/reschedule needs the contractId query parameter to tell which one to reschedule. Without it, or with a contract that isn’t one of them, the call returns 400 with the title UserGeneratedError:Multiple subscriptions share this order, please specify the contract to reschedule and the errorKey ambiguousFulfillmentContract.Reference: Read freeze (skip-billing) status of a contract, Reschedule a planned subscription fulfillment.
Admin APIStorefront APINew endpointNew fields
Retention

Latest discount usage

New GET /subscriptions/cp/api/customer-retention-activities/latest-discount-usage?contractId=… returns the contract’s latest retention activity in which a cancellation-flow discount was applied, or 404 when there’s none. Use it to tell a customer they’ve already had the discount. Customer portal settings return the text for that warning, discountAlreadyUsedWarningText.
Storefront APIShopify FlowNew endpointNew action
Bulk reads, Retry Failed Billing action

Bulk reads for several contracts, and a Retry Failed Billing action

  • New GET /subscriptions/cp/api/campaigns/applicable-bulk and GET /subscriptions/cp/api/subscription-contracts/contract-external-bulk return the applicable campaigns and the raw Shopify contracts for several contracts in one call.
  • The new Shopify Flow action Retry Failed Billing retries a subscription’s most recent failed billing attempt and reuses the failed order instead of creating a new one. It takes contract_id and returns contract_id, billing_attempt_id, success and success_message. success is true once billing has started, not when the payment goes through: the result fires Subscription Billing Success or Subscription Billing Failure. Like Attempt Billing, it needs an Appstle plan that includes immediate order placement.
Reference: List applicable customer portal campaigns for multiple contracts in one call, Shopify Flow actions.
Admin APIStorefront APINew enum valueBehavior changeAction required
Billing attempts, Build-a-Box, bundle discount rules

New values ORPHANED_FREE_PRODUCT and CART_DRAWER

  • partialLinesSkipped has a new value, ORPHANED_FREE_PRODUCT, in PUT /api/external/v2/subscription-billing-attempts and in billing attempt responses.
  • bundleRedirect has a new value, CART_DRAWER, next to CART, CHECKOUT, CUSTOM and NONE, in Build-a-Box responses and in POST and PUT /api/external/v2/build-a-box bodies.
Who is affected: clients that map partialLinesSkipped or bundleRedirect to a fixed list of values.Action required: accept values you don’t recognize.

Bundle discount rules return 400 instead of a server error

GET /subscriptions/cp/api/bundle-discount-rules now returns 400 with the title UserGeneratedError: There is an issue with the Bundle App integration. Please check with the support team. when the store’s Bundles discount rules can’t be loaded. Until now the call failed with a server error (5xx).Who is affected: storefront code that reads bundle discount rules.Action required: handle this 400 the way you handled the server error. A client that retries only on 5xx no longer retries this failure.Reference: List discount rules applicable to bundles.
Storefront APINew endpoint
Bundles

Add a fixed pricing bundle line item

New PUT /subscriptions/cp/api/v2/subscription-contracts-add-bundle-line-item adds a fixed pricing bundle line item, with its child products, to a subscription contract. It refuses a product the customer isn’t allowed to add with the 400 described under August 15.Reference: Add a fixed pricing bundle line item to a subscription contract.
Admin APIStorefront APIBreaking changeAction required
One-time products

One-time products for a specific billing attempt are discontinued

PUT /api/external/v2/subscription-contract-one-offs-by-contractId-and-billing-attempt-id and its Storefront API twin, PUT /subscriptions/cp/api/subscription-contract-one-offs-by-contractId-and-billing-attempt-id, no longer add anything. Every call returns 400 with the title One-time products for a specific billing attempt are no longer supported. and saves nothing.Who is affected: integrations that add a one-time product to a chosen upcoming order through either endpoint.Action required: to add a one-time product to the next order, call PUT /api/external/v2/subscription-contracts-add-line-item with isOneTimeProduct=true, or PUT /subscriptions/cp/api/v2/subscription-contracts-add-line-item with isOneTimeProduct=true from the storefront. The product is removed after that order. The storefront call works only when the merchant has turned on Customers can add one-time products. Otherwise it returns 400 with the title UserGeneratedError:Permission denied, Subscription cannot be modified. There’s no replacement for adding a one-time product to a later order.Reference: Add product to subscription.
JavaScript events
Minimum quantity

Minimum quantity events

  • After it raises cart lines to their selling plan’s minimum quantity, the storefront script dispatches AppstleSubscription:Cart:MinimumQuantityApplied on document and window. event.detail is an array with one { key, minQuantity, quantity } entry per corrected line.
  • On a product page, when the subscription widget raises the quantity input to the selected plan’s minimum quantity, it dispatches AppstleSubscription:SubscriptionWidget:MinimumQuantityApplied on document and window. event.detail is { widgetId, minQuantity }.
Reference: JavaScript events.
Admin APIStorefront APINew enum valueBehavior changeAction required
Activity logs, adding a product

New activity log event type, and a 400 when a customer adds a product during an order

  • eventType in GET /api/external/v2/activity-logs has a new value, BILLING_ATTEMPT_BACKFILLED.
  • PUT /subscriptions/cp/api/v2/subscription-contracts-add-line-item now returns 400 with the title UserGeneratedError:Another billing operation is already in progress. Please wait until all ongoing processes are completed. while a billing attempt for the subscription is in progress. Until now the call went through while the order was being placed.
Who is affected: clients that map eventType to a fixed list of values, and storefront code that lets customers add products.Action required: accept eventType values you don’t recognize. When the storefront add-product call returns this 400, wait until the order has been placed and send the request again.Reference: Search activity logs, Add a line item to a subscription contract.
Storefront APIBehavior changeAction required
Customer portal product rules

One error for products a customer isn’t allowed to add or swap to

When the merchant’s customer portal settings don’t let a customer add a product or swap to it, two Storefront API calls return the same 400. The title is UserGeneratedError:This product is not available for this subscription. and the errorKey is productnotallowedforcustomerportal.
  • PUT /subscriptions/cp/api/v2/subscription-contracts-add-line-item
  • POST /subscriptions/cp/api/subscription-contract-details/replace-variants-v3
Until now PUT /subscriptions/cp/api/v2/subscription-contracts-add-line-item didn’t check these settings and added any product, and the swap call returned the title UserGeneratedError:This product is not available for subscription. with the errorKey productnotallowedforswap.From the storefront, GET /subscriptions/cp/api/data/products1 now lists products by the merchant’s customer portal setting instead of the productSelectionOption you send, and lists every product when purchaseOption is ONE_TIME.Who is affected: storefront code that lets customers add or swap products, or that matches on productnotallowedforswap.Action required: match on the errorKey productnotallowedforcustomerportal, and show the 400 title when a product is refused.Reference: Add a line item to a subscription contract, Replace product variants in a subscription contract.
Admin APIStorefront APINew fields
Selling plans

minQuantity on selling plans

Selling plans return minQuantity (integer), and POST and PUT /api/external/v2/subscription-groups accept it in subscriptionPlans[].
Admin APINew enum valueAction required
Activity logs

New activity log event type DELIVERY_PRICE_SYNC_SKIPPED

eventType in GET /api/external/v2/activity-logs has a new value, DELIVERY_PRICE_SYNC_SKIPPED.Who is affected: clients that map eventType to a fixed list of values.Action required: accept values you don’t recognize.
Admin APIStorefront APINew fieldsBehavior changeAction required
Pickup, contract list, delivery price

Pickup contractId, nextPaidBillingDate in the contract list, and a delivery price check

  • GET /subscriptions/cp/api/pickup/eligible-dates/first, POST /subscriptions/cp/api/pickup/eligible-dates, and GET and POST /subscriptions/cp/api/pickup/eligible-times accept an optional contractId. With it, eligibility is checked against that subscription’s products, starting from its next order date. locationId is now optional on eligible-dates/first and eligible-dates.
  • GET /api/external/v2/subscription-contract-details now fills nextPaidBillingDate for each contract: the next date the customer is actually charged, which skips upcoming orders that a reward makes fully free. Until now it was null in this list.
  • PUT /api/external/v2/subscription-contracts-update-delivery-price now checks that Shopify applied the new price. When Shopify didn’t, usually because the subscription has no delivery method, the call returns 400 with the title UserGeneratedError:Shopify did not apply the new delivery price. This subscription has no delivery method set, so a custom shipping price can't be saved yet. Please set a delivery method first and try again. Until now it returned 200 and the price stayed the same.
Who is affected: integrations that set a delivery price on subscriptions that have no delivery method.Action required: set a delivery method on the subscription before you set its delivery price, and handle this 400.Reference: Retrieve subscription contracts, Update delivery price for a subscription contract.
Admin APIStorefront APINew fields
Free products, contract delivery method

Multiple free products, delivery method type on new contracts

  • Each appstleCycles[] entry returns freeProducts[] (freeVariantId, freeProductHandle, freeProductTitle, freeProductImage) for rewards of discount type FREE_PRODUCT. It holds up to 5 free products. Entry 0 is always copied to the older single freeVariantId, freeProductHandle, freeProductTitle and freeProductImage fields, so clients that read only those keys keep working. When freeProducts is absent, those single fields describe the one free product.
  • POST /api/external/v2/subscription-contract-details/create-subscription-contract accepts methodType (SHIPPING, LOCAL or PICK_UP). LOCAL requires deliveryPhone, and PICK_UP requires locationId, a Shopify location ID.
Admin APINew endpoint
Retention

Customer retention activities

New GET /api/external/v2/customer-retention-activities lists customer retention activities, filtered by contractId, eventSource, status, retentionAction, retentionReason, fromDay and toDay, and paged.Reference: Get customer retention activities.
Admin APIStorefront APIBehavior change
Discount codes

A clear error for a discount code on a $0 subscription

PUT /api/external/v2/subscription-contracts-apply-discount and PUT /subscriptions/cp/api/subscription-contracts-apply-discount return 400 with the title UserGeneratedError:This subscription already has a total price of $0. An additional discount cannot be applied. and the errorKey subscriptionTotalAlreadyZero when the subscription’s line items already total 0 (the delivery price isn’t counted). The Shopify Flow action that applies a discount code fails the same way. The call also writes a failed discount entry to the subscription’s activity log.Reference: Apply discount code to subscription, Apply a discount code to a subscription contract.
Admin APIStorefront APINew fields
Cancellation offers, Build-a-Box

reasonId on cancellation offers, inventory tracking mode

  • POST /subscriptions/cp/api/cancellation-retention-offers/execute and PUT /subscriptions/cp/api/subscription-contracts-cancellation-discount accept reasonId.
  • Build-a-Box bundles return and accept inventoryTrackingMode (ADD_CHILD_PRODUCT_ON_ORDER or ADD_CHILD_PRODUCT_ON_CHECKOUT).
Admin APIStorefront APINew fieldsBehavior changeAction required
Cancellation reasons, live order status, pickup selection

Translated cancellation reasons, custom attributes on live order status

  • GET /api/external/v2/cancellation-managements/{id} and its Storefront API twin accept languageCode and return reasonMessageTranslationsJSON.
  • GET /subscriptions/cp/api/orders/{orderId}/live-status returns lineItems[].customAttributes.

Pickup and delivery selections update the delivery price

POST /subscriptions/cp/api/pickup/subscription-selection now sets the subscription’s delivery method and delivery price in the same request when the new selection changes the delivery type, the location or the rate. A change to date, time or note alone doesn’t. The call does this only when Appstle calculates the subscription’s delivery price: the subscription has no custom delivery price, and the store calculates shipping prices automatically or recalculates them when the delivery method changes. Until now, the call saved only the selection, and the delivery price was recalculated in the background afterwards.When the new rate can’t be verified, the call returns 400 and changes nothing. The title ends with The delivery method was not changed. The errorKey is one of:
  • pickupRatesUnavailable: for a shipping selection, the store’s Pickup & Delivery rates couldn’t be loaded.
  • shippingRateUnavailable: for a shipping selection, no eligible Shopify shipping rate is available.
  • pickupRateUnavailable: for a pickup or delivery selection, the selected rate couldn’t be verified.
Who is affected: custom pickup and delivery pickers in a customer portal you build.Action required: handle this 400 and show the customer the message after UserGeneratedError:. After a successful call, read the subscription again to show its new delivery price.Reference: Update pickup and delivery details on a subscription contract.
Storefront APINew endpointNew fields
Pickup

Pickup times

New POST /subscriptions/cp/api/pickup/eligible-times lists eligible pickup times for a date. GET /subscriptions/cp/api/pickup/eligible-times and POST /subscriptions/cp/api/pickup/eligible-dates accept a timeZone query parameter, and POST /subscriptions/cp/api/pickup/subscription-selection accepts timeZone in the body.Reference: List eligible pickup times for a date.