- 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/.
Pagination Link URLs start with https://
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.preservePortalOrderNote
GET /subscriptions/cp/api/shop-infos-by-current-login returns preservePortalOrderNote (boolean).Reference: Get shop info for the currently signed-in shop.Pickup and delivery visibility in portal settings, and no gift cards as free products
GET /api/external/v2/customer-portal-settings/{id}andGET /subscriptions/cp/api/customer-portal-settings/{id}return three new booleans:apdHideDelivery,apdHidePickupandapdHideShipping.POST /api/external/v2/subscription-groups, andPUTfor new plans, return400when a free product inappstleCycles[]is a gift card. The title isUserGeneratedError:followed by the product’s name andis a gift card. Shopify doesn't allow free gift cards on subscription orders, so choose a different free product.
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.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 (afterCycle4) withrepeatEvery5 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 highestafterCycleamong them. It’s ignored on pay-as-you-go prepaid plans. repeatEveryis in the responses ofGET /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-plansandGET /subscriptions/cp/api/subscription-groups/all-selling-plans. It’s accepted inPOST /api/external/v2/subscription-groups, in new plans (withoutidNew) inPUT /api/external/v2/subscription-groups, and inlines[].pricingPolicy[]ofPOST /api/external/v2/subscription-contract-details/create-subscription-contract.PUTignoresappstleCycles[]on plans that already exist.- On the subscription group calls, a
repeatEverythat isn’t a whole number from 1 to 150 returns400with the titleUserGeneratedError:Repeat every has to be a whole number of orders from 1 to 150.Repeating rewards in one plan share onerepeatEvery, so different values return400with the titleUserGeneratedError: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 returns400with a title that startsUserGeneratedError:With repeat every. GET /subscriptions/cp/api/data/v2/products-selling-plansreturnsfrequencyNameTranslationsfor 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.
singleOrder on cycle rewards, and stopSwapEmails on swaps
- Each entry of
appstleCycles[]carries a new field,singleOrder(boolean). It applies toSHIPPINGrewards 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 acceptrepeatEvery, listed under October 26, return and accept it too. POST /api/external/v2/subscription-contract-details/replace-variants-v3now honorsstopSwapEmails. Withtrue, the swap sends no email notifications. Until now the endpoint ignored the field and always sent them.
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.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 inconfigJSON, a JSON string. In it, aTREATMENT_SETstep’spayload, or that payload’sfallback, can haveactionFREE_GIFT, withfreeGiftVariantIdand, 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
flowIdandflowReasonIdcarries a Free Gift offer,POST /subscriptions/cp/api/cancellation-retention-offers/executeruns it the way it does for a reason-based offer (see October 16):retentionAction9, the gift variant added to the next order as a free one-time item, once per subscription.
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.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.Activity logs accept the X-API-Key header, and the countries list covers every shipping zone
GET /api/external/v2/activity-logsnow accepts the API key in theX-API-Keyheader, like the other Admin API endpoints. Until now, a request that sent the key only in the header returned400, because theapi_keyquery parameter was required.api_keystill works but is deprecated.- On a store that doesn’t use Shopify Markets shipping,
GET /subscriptions/cp/api/v2/data/countriesnow 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.
Bundle display settings and freeze setting
GET /api/external/v2/subscription-bundle-settings/{id}andGET /subscriptions/cp/api/subscription-bundle-settings/{id}returnhideProductTagsandshowFullProductDescription(booleans).- Customer portal settings return
allowAddProductWhenContractIsFreeze(boolean). When it’strue,PUT /subscriptions/cp/api/v2/subscription-contracts-add-line-itemandPUT /subscriptions/cp/api/v2/subscription-contracts-add-bundle-line-itemadd products even while the store locks changes until a subscription reaches its minimum number of orders.
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:BuildABoxDiscountRemovedondocumentonly, not onwindow, when it removes a Build-a-Box discount code from a cart that has no box items.event.detail.codeslists the removed codes. GET /api/external/v2/cancellation-managements/{id}andGET /subscriptions/cp/api/cancellation-managements/{id}can return a cancellation reason, or a reason’sfallbackOffer, withcancellationAction9(Free Gift),freeGiftVariantIdand, when the merchant set one,freeGiftTitle. The reasons are incancellationReasonsJSON, a JSON string.POST /subscriptions/cp/api/cancellation-retention-offers/executecan run a Free Gift offer. The response then carriesretentionAction9andretentionActionLabelFree Gift, andretentionActivity.additionalInfo(a JSON string) holdsfreeGiftVariantIdand, 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.
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.
Keep a custom delivery price for one subscription
NewPUT /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.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), andPOSTandPUT /api/external/v2/subscription-groupsaccept it insubscriptionPlans[]. - 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. WithoutcontractIds, it returns an empty object. eventTypeinGET /api/external/v2/activity-logshas a new value,SEND_GIFT_PURCHASED_MERCHANT_EMAIL.PUT /subscriptions/cp/api/subscription-billing-attempts/attempt-billing/{id}accepts an optionalscheduleModequery parameter that sets how ordering now affects the schedule.extra_order_onlyplaces an extra order and leaves the upcoming orders as they are.extra_order_recalculateplaces an extra order and recalculates the upcoming order dates. Without it, the store’s Manual Order Processing setting applies.- Send
scheduleModeonly when customer portal settings returnallowOrderNowScheduleChoiceastrue. Otherwise the call returns400with the titleUserGeneratedError:This schedule option is no longer available. Please refresh the page and try again.An unrecognized value returns400with the titleUserGeneratedError:Invalid schedule option. Please refresh the page and try again. - Customer portal settings return
allowOrderNowScheduleChoiceandhideRescheduleTimeInExtension(booleans). POSTandPUT /api/external/v2/subscription-groupsreturn400when a plan hasgifttrueand agiftSendMaxDaysoutside 1 to 365, with the titleUserGeneratedError:Gift invitations can be scheduled between 1 and 365 days ahead.and theerrorKeyinvalidGiftSendMaxDays, or aminQuantityabove1, with the titleUserGeneratedError:Gifts must be purchased one at a time. Leave minimum quantity empty or set it to 1.and theerrorKeyinvalidGiftMinimumQuantity.PUT /api/external/v2/subscription-contracts-update-max-cyclesreturns400with the titleUserGeneratedError:Gift subscriptions end after their prepaid deliveries, so their maximum can't be changed.and theerrorKeygiftMaxCyclesChangeBlockedwhen the subscription is a gift subscription. The Shopify Flow action Update Max Cycles fails on gift subscriptions for the same reason.
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
eventTypeinGET /api/external/v2/activity-logshas two new values:SEND_GIFT_RECEIVED_EMAILandSEND_GIFT_THANK_YOU_EMAIL.- Selling plans return
gift(boolean), andPOSTandPUT /api/external/v2/subscription-groupsaccept it insubscriptionPlans[]. GET /subscriptions/cp/api/data/v2/products-selling-plansaccepts acontractIdquery parameter.POST /api/external/v2/subscription-groups, andPUTfor new plans, return400with the titleUserGeneratedError:Remove rewards before selling this plan as a gift.and theerrorKeygiftRewardsNotSupportedwhen a plan hasgifttrueand rewards inappstleCycles[].- The same calls return
400with the titleUserGeneratedError:Each new gift option needs its own name.and theerrorKeygiftFrequencyNameNotUniquewhen a new plan withgifttruehas the samefrequencyNameas another plan in the request, ignoring case, and with the titleUserGeneratedError:Gift plans must use Prepaid One-Time with one billing cycle.and theerrorKeyinvalidGiftPlanwhen a plan withgifttruehas aplanTypeother thanPREPAIDor amaxCyclesother than1.
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-priceorPUT /api/external/v2/subscription-contracts-update-line-item-pricing-policy. It also applies when you trigger billing withPUT /api/external/v2/subscription-billing-attempts/attempt-billing/{id}for a subscription with no original order amount on file. - The
400messages for this limit now show it with its currency code, for example500.00 USD. Until now they showed only500. - When Appstle has no exchange rate for the subscription’s currency, these calls return
400with the titleUserGeneratedError:Unable to verify this subscription's safety limit. Please try again or contact Appstle support.and theerrorKeycharge_cap_currency_unavailable.
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.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
400with the titleUserGeneratedError:Permission denied, Subscription cannot be modified. - If the merchant limits cancellation by how close the next billing date is, the call returns
400with theerrorKeytimeBasedRestriction. Its title isUserGeneratedError:followed by a message that can be the merchant’s own text, so match theerrorKey, 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.allowOrderNowOnListPage
Customer portal settings return allowOrderNowOnListPage (boolean).Pickup data on live order status, and a billing-date guard
GET /subscriptions/cp/api/orders/{orderId}/live-statusacceptsincludePickupDeliveryData(boolean) and then returnspickupDeliveryData(string).PUT /subscriptions/cp/api/subscription-billing-attempts/skip-order/{id}andPUT /subscriptions/cp/api/subscription-contracts-update-billing-dateaccept an optionalexpectedBillingDatequery parameter, andupdate-billing-datealso acceptsexpectedBillingAttemptId. Send the upcoming order’sbillingDateexactly as the API returned it. Forupdate-billing-date, also send that order’s billing attemptid. If the upcoming order has changed since, the call changes nothing and returns400with the titleUserGeneratedError:This upcoming order has changed. Review the latest order before trying again.
showSwapOnlyForOutOfStockBABProducts
Customer portal settings return showSwapOnlyForOutOfStockBABProducts (boolean).Customers can’t go below a plan’s minimum quantity
When a customer lowers a line’s quantity withPUT /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.Refused delivery addresses say what’s wrong
When Shopify refuses the address itself (its message starts withDelivery 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/executeskips the number of orders the merchant set on the cancellation reason when it runs a skip offer.skipCyclein the request counts only when the shop’s cancellation management settingdynamicallyPauseCycleOnCancellationis on and the request has noflowIdorflowReasonId. It must then be between1and the configured number, or the call returns400with the titleUserGeneratedError:Invalid skip duration for this retention offer.Otherwise the call ignoresskipCycle. Until now, anyskipCyclereplaced the configured number.PUT /subscriptions/cp/api/subscription-contract-detailsreturns400with the titleUserGeneratedError:Permission denied, Order notes are not allowedwhen 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-attributesandPUT /subscriptions/cp/api/subscription-contracts-edit-line-item-attributeskeep the attributes Appstle sets on a line, such as_appstle-bb-idand_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 returns400with the titleUserGeneratedError:Permission denied, Editing attributes is not allowed. A request that repeats a key returns400with the titleUserGeneratedError:Duplicate custom attribute key.PUT /subscriptions/cp/api/update-classic-bab-subscription/{contractId}checks each line it changes. It returns400with the titleUserGeneratedError: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 allowedfor a product the merchant has locked, andUserGeneratedError:Permission denied, Removing products from subscription is not allowedfor a quantity of0when customers can’t remove products. A line that must stay for a minimum number of orders returns400until it has, with a title that isUserGeneratedError: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.
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/approvedby default. A call made with the token as soon as it arrives no longer returns401. - Any
2xxreply from the callback now counts as received, whatever its body. Until now, a2xxreply whose body wasn’t a JSON object failed the approval. - When the callback doesn’t answer with a
2xxstatus within 5 seconds, the approval fails: Appstle deletes the token it created, leaves the connection pending, and returns400with theerrorKeyPARTNER_UNREACHABLEto the merchant. The partner gets no usable token from that attempt. See What if the callback fails?. POST /api/partner/{partnerId}/verifynow returns{ "verified": false }for acallback_noncethat is more than 5 minutes old, as the partner guide already said. Until now, an expired nonce could still be verified.
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.Offer tier on cancellation discounts, and no merging of cancelled subscriptions
PUT /subscriptions/cp/api/subscription-contracts-cancellation-discountaccepts an optionalofferTierquery parameter,PRIMARYorFALLBACK, that records which cancellation offer the customer accepted. It’s saved in the retention activity’sadditionalInfo.offerTier.POST /subscriptions/cp/api/subscription-contract-details/merge-contractreturns400with the titleUserGeneratedError:Cancelled subscriptions cannot be merged. Please refresh the page and try again.when either subscription is cancelled. Until now the call went ahead.
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 withapst_aren’t affected. - Each
appstleCycles[]entry returnsrestrictFreeProductQuantity(boolean). Whentrue, each free product stays at quantity 1 regardless of the parent line item’s quantity. It’s returned and accepted whereverrepeatEveryis (see October 26).
apst_ key under More → API Access and move your integration to it before the merchant revokes the legacy key.Reference: Managing keys.Plans need their own names, and rewards sent with new plans are saved
POST /api/external/v2/subscription-groupsandPUT /api/external/v2/subscription-groupsreturn400with the titleUserGeneratedError:Each subscription option needs its own name. Rename one of the duplicate options.when two plans in the request have the samefrequencyName. The comparison ignores spaces and letter case. OnPUT, the call refuses a duplicate only when at least one of the two plans is new, meaning it has noidNew. Until now, both calls saved duplicate names.appstleCycles[]sent with a plan inPOST /api/external/v2/subscription-groups, or with a new plan inPUT /api/external/v2/subscription-groups, is checked and saved as that plan’s rewards. Until now both calls dropped it.PUTstill ignoresappstleCycles[]on plans that already exist. A reward the store can’t use returns400, for exampleUserGeneratedError:Your current plan doesn't include subscription rewards. Upgrade your plan to add or change rewards.orUserGeneratedError:Select the free product for every free-gift reward before saving.
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.Trigger immediate billing returns an acknowledgement
PUT /api/external/v2/subscription-billing-attempts/attempt-billing/{id}used to return200with an empty body. It now returns200withbillingAttemptId,requestedBillingAttemptId,contractId,billingDate,statusandmessage. 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.billingAttemptIdis the attempt that was queued, and it can differ from theidyou sent (returned asrequestedBillingAttemptId).statusis the attempt’s status when it was queued, for exampleIMMEDIATE_TRIGGERED.- When no billing attempt could be triggered, the call returns
400with the titleUserGeneratedError:Billing attempt could not be triggered. GET /subscriptions/cp/api/delivery-profiles/get-locationsreturnsfulfillsOnlineOrders(boolean) for each location.
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.Delivery type, shipping model and Markets shipping options
GET /subscriptions/cp/api/data/contract-live-shipping-ratesreturnsdeliveryTypefor each rate.GET /subscriptions/cp/api/v2/data/countriesaccepts anincludeShippingModelquery parameter.deliveryMethodIdis optional onPUT /api/external/v2/bulk-automations/delivery-method.- On a store that uses Shopify Markets shipping,
GET /subscriptions/cp/api/data/shop-delivery-optionsfirst returns one item per market that has active shipping options, as{ name, marketOptions }, wheremarketOptionslists 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-optionsnow 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-methodandPUT /subscriptions/cp/api/subscription-contracts-update-delivery-methodreturn400with the titleUserGeneratedError:The selected shipping option is no longer available.and theerrorKeyshippingOptionUnavailablewhendelivery-method-idnames a shipping option that no longer exists.
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.stickyCheckoutButton
Bundle settings return stickyCheckoutButton (boolean).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.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 theerrorKeyisinvalidSplitProductswhen the list is empty, names a line that isn’t in the subscription, or names a free gift or a one-time product. - With
attemptBillingtrue, the title isUserGeneratedError:The selected products are skipped for the next order. Unskip a product before ordering it now, or split without placing an order.and theerrorKeyissplitProductsSkippedwhen every selected product is skipped for the next order. - With
isSplitContracttrue, the title isUserGeneratedError: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 titleUserGeneratedError:All products cannot be selected, at least one product is required in the current contract. - With
isSplitContracttrue, 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 returns400with the titleUserGeneratedError:The products could not be removed from the original subscription. No immediate order was requested. Contact support before trying again.and theerrorKeysplitRemovalFailed. WithattemptBillingtrue, the new subscription’s order is placed only after the products are removed.
400 title when a call is refused.Reference: Split or duplicate an existing subscription contract, Split or duplicate an existing subscription contract (Storefront API).Error responses use appstle.com problem types and hide internal messages
The application/problem+json error responses changed in two ways:- The
typeof an error now starts withhttps://appstle.com/problem/, for examplehttps://appstle.com/problem/problem-with-message. Before, it started with a different base URL. detailno longer carries internal error text. A server error (5xx) has thedetailAn unexpected error occurred. Please try again later.A400for a path or query parameter of the wrong type, such as a customer portal settings ID that isn’t a number, has thedetailUnexpected runtime exception. A400for a request body that isn’t valid JSON has thedetailUnable to convert http message.
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.whencountryCodeisn’t a valid country code.addressRequired:UserGeneratedError:Enter a street address.whenaddress1is blank.locationRequired:UserGeneratedError:Choose a pickup location.when the delivery method is local pickup andlocationIdis 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.
UserGeneratedError: when a call is refused.Reference: Update subscription delivery address and method, Update subscription delivery address and method (Storefront API).New activity log entity type and line item selling plan name
entityTypeinGET /api/external/v2/activity-logshas a new value,QUICK_ACTION_LINK.GET /subscriptions/cp/api/orders/{orderId}/live-statusreturnslineItems[].sellingPlanName.
entityType to a fixed list of values.Action required: accept values you don’t recognize.Requests can’t name another customer or store
- A Storefront API request that names a
customerIdother than the signed-in customer’s returns403with thedetailCustomer does not match the authenticated session. This coverscustomerIdandcustomer_idin the query string (for exampleGET /subscriptions/cp/api/loyalty-integration/earn-options?customerId=...) and in the request body (for examplePUT /subscriptions/cp/api/subscription-contract-details). - A request body whose
shop,shopName,shopDomainorshop_domainfield names another store returns403with thedetailShop does not match the authenticated session. This covers Storefront API request bodies, and Admin API request bodies that have one of these fields, such asPUT /api/external/v2/product-swaps. The same check on query parameters started on September 6.
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.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 thebundleTopHtml 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.removeDiscount is honored, and fixed date schedules allow 24 dates
PUT /api/external/v2/subscription-contracts-remove-line-itemnow honorsremoveDiscount=falseand 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. WithoutremoveDiscount, or withtrue, nothing changes.POSTandPUT /api/external/v2/subscription-groupsaccept up to 24 entries in a plan’sfixedBillingDates, up from 12. A request with more than 24 returns400with the titleFixed date schedules support up to 24 billing dates a year. The title used to sayup to 12.
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.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.Query parameters can’t name another store
A request whoseshop, 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.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-productstakes the same body asPOST /api/external/v2/subscription-groups/{id}/bulk-add-products(productIdsandvariantIds, at least one of them). It returns202right away withstatusprocessingandsubscriptionGroupId, and adds the products in the background. - One job runs per store at a time. A second
POSTwhile a job is running returns400with the titleUserGeneratedError: A bulk add-products job for this shop is already in progress. APOSTfor an unknown group ID returns400with the titleInvalid id. - New
GET /api/external/v2/subscription-groups/v3/{id}/add-products/statusreports the store’s latest job:found,running,executionStatus(for exampleRUNNING,SUCCEEDEDorFAILED),errorInfo,subscriptionGroupIdandmatchesRequestedGroup. Poll it untilrunningisfalse. IfmatchesRequestedGroupisfalse, 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 onlyfound: false,running: falseandexecutionStatus: null, whatever the group ID.
Contract revisionId
Contract responses, for example GET /api/external/v2/subscription-contracts/contract-external/{contractId}, return revisionId.Live carrier-quoted shipping rates
- New
GET /subscriptions/cp/api/data/contract-live-shipping-rateslists live carrier-quoted shipping rates for a subscription contract. PUT /subscriptions/cp/api/subscription-contracts-update-delivery-methodaccepts acarrier-rate(boolean) query parameter, andGET /subscriptions/cp/api/shop-infos-by-current-loginreturnsdisableShippingPricingAutoCalculationandrecalculateShippingPriceBeforeBilling.
Read the freeze status of a contract, and reschedule a fulfillment shared by several subscriptions
NewGET /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.Latest discount usage
NewGET /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.Bulk reads for several contracts, and a Retry Failed Billing action
- New
GET /subscriptions/cp/api/campaigns/applicable-bulkandGET /subscriptions/cp/api/subscription-contracts/contract-external-bulkreturn 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_idand returnscontract_id,billing_attempt_id,successandsuccess_message.successistrueonce 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.
Admin APIStorefront APINew enum valueBehavior changeAction required
Billing attempts, Build-a-Box, bundle discount rules
New values ORPHANED_FREE_PRODUCT and CART_DRAWER
partialLinesSkippedhas a new value,ORPHANED_FREE_PRODUCT, inPUT /api/external/v2/subscription-billing-attemptsand in billing attempt responses.bundleRedirecthas a new value,CART_DRAWER, next toCART,CHECKOUT,CUSTOMandNONE, in Build-a-Box responses and inPOSTandPUT /api/external/v2/build-a-boxbodies.
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.Add a fixed pricing bundle line item
NewPUT /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.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.Minimum quantity events
- After it raises cart lines to their selling plan’s minimum quantity, the storefront script dispatches
AppstleSubscription:Cart:MinimumQuantityAppliedondocumentandwindow.event.detailis 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:MinimumQuantityAppliedondocumentandwindow.event.detailis{ widgetId, minQuantity }.
New activity log event type, and a 400 when a customer adds a product during an order
eventTypeinGET /api/external/v2/activity-logshas a new value,BILLING_ATTEMPT_BACKFILLED.PUT /subscriptions/cp/api/v2/subscription-contracts-add-line-itemnow returns400with the titleUserGeneratedError: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.
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.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 same400. 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-itemPOST /subscriptions/cp/api/subscription-contract-details/replace-variants-v3
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.minQuantity on selling plans
Selling plans return minQuantity (integer), and POST and PUT /api/external/v2/subscription-groups accept it in subscriptionPlans[].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, andGETandPOST /subscriptions/cp/api/pickup/eligible-timesaccept an optionalcontractId. With it, eligibility is checked against that subscription’s products, starting from its next order date.locationIdis now optional oneligible-dates/firstandeligible-dates.GET /api/external/v2/subscription-contract-detailsnow fillsnextPaidBillingDatefor each contract: the next date the customer is actually charged, which skips upcoming orders that a reward makes fully free. Until now it wasnullin this list.PUT /api/external/v2/subscription-contracts-update-delivery-pricenow checks that Shopify applied the new price. When Shopify didn’t, usually because the subscription has no delivery method, the call returns400with the titleUserGeneratedError: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 returned200and the price stayed the same.
400.Reference: Retrieve subscription contracts, Update delivery price for a subscription contract.Multiple free products, delivery method type on new contracts
- Each
appstleCycles[]entry returnsfreeProducts[](freeVariantId,freeProductHandle,freeProductTitle,freeProductImage) for rewards of discount typeFREE_PRODUCT. It holds up to 5 free products. Entry 0 is always copied to the older singlefreeVariantId,freeProductHandle,freeProductTitleandfreeProductImagefields, so clients that read only those keys keep working. WhenfreeProductsis absent, those single fields describe the one free product. POST /api/external/v2/subscription-contract-details/create-subscription-contractacceptsmethodType(SHIPPING,LOCALorPICK_UP).LOCALrequiresdeliveryPhone, andPICK_UPrequireslocationId, a Shopify location ID.
Customer retention activities
NewGET /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.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.reasonId on cancellation offers, inventory tracking mode
POST /subscriptions/cp/api/cancellation-retention-offers/executeandPUT /subscriptions/cp/api/subscription-contracts-cancellation-discountacceptreasonId.- Build-a-Box bundles return and accept
inventoryTrackingMode(ADD_CHILD_PRODUCT_ON_ORDERorADD_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 acceptlanguageCodeand returnreasonMessageTranslationsJSON.GET /subscriptions/cp/api/orders/{orderId}/live-statusreturnslineItems[].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 ashippingselection, the store’s Pickup & Delivery rates couldn’t be loaded.shippingRateUnavailable: for ashippingselection, no eligible Shopify shipping rate is available.pickupRateUnavailable: for apickupordeliveryselection, the selected rate couldn’t be verified.
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.Pickup times
NewPOST /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.