- 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://loyalty-admin.appstle.com, and Storefront API paths start with/loyalty/cp/api/.
Stricter validation on points, approvals and birth dates
These calls now return400 for requests they used to accept. The problem title in the response says what’s wrong.POST /api/external/add-pointsandPOST /api/external/remove-pointsreject apointsvalue of0or less with the titlePoints must be greater than 0. Until now,0and negative values were accepted. To take points away, callremove-pointswith a positive number.add-pointsrejects anearnRuleIdwhose earn rule is turned off, with the titlePoint Earn Rule is not active. It already rejected an unknown rule and a rule outside its scheduled dates. The same check applies to the Rule Id field of the Shopify Flow Add Points action.PUT /api/external/approve-pending-transactionsapproves every pending transaction only when you setapproveAllPendingTransactions=true. A call that sends no approval list (or an empty one) and leaves outapproveAllPendingTransactionsnow returns400with the titlePlease provide an approval list or set approveAllPendingTransactions to true. Until now, that call returned204and approved every pending transaction in the store.PUT /api/external/update-customer-birth-datevalidatesdobthe way the Storefront API already did. A missingdob, a year before 1900 or a date in the future returns400with the titleUserGeneratedError:Please enter a valid birthdate.When you senddobYearProvided: false, only a missingdobis rejected. An unknowncustomerIdnow returns400with the titleCustomer not found using this customerIDinstead of an empty500.
0 or negative points, pass the ID of a turned-off earn rule, call approve-pending-transactions without a list to approve everything, or send birth dates without validating them.Action required: send a positive points value, and send an earnRuleId only for an active earn rule. To approve every pending transaction, set approveAllPendingTransactions=true. Validate birth dates before you send them. Handle 400 on these calls and log the title.Reference corrections
The API reference now matches what these endpoints have always done. Their behavior didn’t change.GET /api/external/customer-loyaltyreturns400, not404, when the customer isn’t found. The title isCustomer not found using this customerID,Customer not found using this email, orCustomer info not foundwhen you send neithercustomerIdnorcustomerEmail. Withoutstatus, onlyUNUSEDrewards are returned.statustakes one or more ofUNUSED,USED,EXPIRED,SUBSCRIPTION_ACTIVE,REFUNDEDandCANCELED, separated by commas, and any other value returns400.PUT /api/external/check-discountreturns the reward whatever its status, so checkstatusbefore you apply the code. An unknown code returns400with the titleDiscount is not found, not404.- When the earn rule you pass to
add-pointsrequires manual approval, the points stay pending until you approve them withapprove-pending-transactions.
Social media claims return the link to open
WhenPOST /loyalty/cp/api/claim-social-media-points awards points, the response includes redirectUrl, the social media link set on the earn rule. Open it for the customer after the claim. A refused claim returns redirectUrl as null.Reference: Claim social media reward points.Add Points and Remove Points apply once per action run
Shopify Flow resends an action with the same action run ID when it doesn’t get a response in time. The Add Points and Remove Points actions now apply the points once per action run, so a resent action no longer adds or removes them a second time.Who is affected: workflows that guard against doubled points, for example with a tag check before the action. You can remove that guard.Reference: Shopify Flow.Return an unused reward for its points
NewPOST /loyalty/cp/api/return-reward/{rewardId} lets a signed-in customer hand back a reward they haven’t used and get its points back. The discount code is removed from Shopify and the points are credited as a REDEEM_REVERSAL transaction. The response is the customer’s updated loyalty details, in the same shape as redeem-points returns.It works only when the merchant has turned on Let customers return unused rewards in Advanced Settings. Only a discount code the customer bought with points can be returned, and only while it’s unused and hasn’t expired. Store credit and gifted rewards (VIP tier, birthday, referral) can’t be returned.Otherwise the call returns 400. The title explains why, and message is error. followed by one of rewardReturnDisabled, idnotfound, rewardNotUnused, rewardNotReturnable, rewardExpired, rewardUsageUnverified or rewardActionInProgress. The last two are temporary, so the customer can try again.Who is affected: storefronts that list a customer’s rewards. Nothing changes until you call the endpoint.Reference: Return an unused reward for its points.Merchants can revoke the legacy API key
In the Appstle admin, under More → API Integrations, the Legacy API Key card has a Revoke button. After a merchant revokes it, every request that sends that key returns401 with {"status":401,"detail":"Invalid authentication"}, the same response as any other wrong key (see Error responses). Appstle doesn’t issue a replacement. Keys that start with apst_ aren’t affected.Who is affected: integrations that still send the legacy key, the one the app showed before apst_ keys existed.Action required: create an apst_ key under More → API Integrations and move your integration to it before the merchant revokes the legacy key.Hover labels for reward activation and expiry
GET /api/external/widget-labels returns two new label fields: rewardActivationDelayHoverLabel and rewardExpirationHoverLabel.Reference: List storefront widget label translations.Redeeming a reward can fail when its Shopify discount has expired
POST /api/external/redeem-points and POST /loyalty/cp/api/redeem-points now return 400 with the title UserGeneratedError:This reward is temporarily unavailable. Please try again later or contact the store. when the Shopify discount behind the redeem rule has expired. Before, Appstle issued a code that Shopify then rejected at checkout, and the customer lost the points. Since October 22, Appstle first tries to renew the expired discount and returns this error only when it can’t. The customer’s points aren’t deducted when the call fails.Show the title to the customer or log it, and retry later. Tell the merchant to check the redeem rule’s discount in Shopify.Subscription and sign-up settings on earn rules
GET /api/external/point-earn-rules returns three new booleans on each rule:earnOnInitialSubscriptionOrderandearnOnRecurringSubscriptionOrdersay whether a purchase rule awards points for subscription products on a subscription’s first order and on its renewal orders. When one is missing, treat it astrue.awardExistingCustomersapplies to the sign-up rule. When it’strue, customers who joined before the rule was created can still earn its points. When it’s missing, treat it asfalse.
Greeting icon in widget settings
GET /api/external/widget-labels returns a new field, customerGreetingsIcon.Reference: List storefront widget label translations.Recurring schedule on earn rules
GET /api/external/point-earn-rules returns ruleRecurrence, a JSON string that limits a rule to certain days on top of its start and end dates. It holds two lists: weeks, the weeks of the month (week 1 is days 1 to 7), and days, the days of the week (MONDAY to SUNDAY). Both are read in the store’s time zone, and an empty list means any week or any day. ruleRecurrence is empty when a rule has no recurrence.Reference: Get all point earning rules.Error key for self-referrals
GETandPOST /loyalty/cp/api/referral-rules/accept-offerreturnerrorKeySELF_REFERRALwhen a customer tries to accept their own referral offer. The response was already400withstatusFAILED. Until now,errorKeywas empty for this case.GET /api/external/widget-labelsreturnsselfReferralErrorLabel, the text to show for that error.
errorKey to a fixed list of values.Action required: accept values you don’t recognize.Reference: Accept referral offer (POST).Label for unused rewards
GET /api/external/widget-labels returns a new field, unusedRewardsLabel.Reference: List storefront widget label translations.existingCustomer on referral offers
GET and POST /loyalty/cp/api/referral-rules/accept-offer return existingCustomer (boolean). On success, it’s true when the referred email already belongs to a customer of the store. GET /api/external/widget-labels returns referralExistingCustomerAcceptedText.Reference: Accept referral offer (POST).Turned-off redeem rules can’t be redeemed
POST /api/external/redeem-points and POST /loyalty/cp/api/redeem-points return 400 with the title Redeem rule not available when the redeem rule is turned off, the same title as for an unknown rule. Until now, a turned-off rule could still be redeemed through the API.Who is affected: integrations that store redeem rule IDs and keep using them after the merchant turns a rule off.Action required: take redeem rule IDs from GET /api/external/point-redeem-rules, which lists only active rules, and handle 400 on redeem-points.Reference: Redeem customer points for rewards, Get all point redemption rules.Delayed reward activation
- Redeem rules (
GET /api/external/point-redeem-rules) returnactivationDelayDays, the number of days before a redeemed reward becomes usable. - Rewards in
customer-loyalty,add-points,add-credits,redeem-points,remove-pointsandupdate-discount-code-statusresponses carryactiveFrom, the date the reward becomes usable.check-discountandupdate-discountreturnactiveFromtoo. - The Storefront API returns
rewards[].activeFromfromGET /loyalty/cp/api/customer-loyaltyandPOST /loyalty/cp/api/redeem-points.
activeFrom first so you don’t present a code that doesn’t work yet.Reference: Get all point redemption rules, Get customer loyalty details.Storefront APIBreaking changeNew enum valueAction required
Storefront API writes, social media points
Storefront API writes need X-Requested-With
Every Storefront API request that isn’t a GET, HEAD or OPTIONS must send the header X-Requested-With: XMLHttpRequest. Without it, the call returns 401 with {"status":401,"detail":"Invalid authentication"}, even for a signed-in customer.Who is affected: storefront code that sends a Storefront API POST, PUT or DELETE without that header.Action required: add X-Requested-With: XMLHttpRequest to every Storefront API write call.New refusal reason for social media claims
POST /loyalty/cp/api/claim-social-media-points can return awarded false with the reason CREDIT_PENDING_RECONCILIATION. This happens only for earn rules that give store credit: the claim is recorded, but Appstle couldn’t confirm that the store credit was added. The message asks the customer to contact support before trying again.Who is affected: clients that map reason to a fixed list of values.Action required: accept reason values you don’t recognize, and show the message for them.Reference: Enroll customer in loyalty program, Claim social media reward points.excludeProductData on redeem rules
GET /api/external/point-redeem-rules returns excludeProductData on each rule: a JSON string that lists the products and variants the reward’s discount doesn’t apply to. It’s empty when nothing is excluded.Reference: Get all point redemption rules.Earn rule description labels
GET /api/external/widget-labels returns three new label fields: buyProductRuleDescriptionLabel, buyProductFromCollectionRuleDescriptionLabel and earnRuleShowMoreLabel.Reference: List storefront widget label translations.Birth year is optional on birthday updates
PUT /api/external/update-customer-birth-dateandPOST /loyalty/cp/api/update-customer-birth-dateacceptdobYearProvided(boolean). Whenfalse, the year indobis a placeholder and only the month and day are used to match birthday rewards. It defaults totruewhen omitted, so existing clients behave as before.customer-loyalty,add-points,add-credits,redeem-points,remove-points,update-discount-code-statusandupdate-customer-birth-dateresponses returndobYearProvided, and so doesPOST /loyalty/cp/api/update-customer-status.- The birthday earn rule returns
collectBirthYear. When it’sfalse, the merchant has turned off Ask for birth year, so collect only the month and day and senddobYearProvided: false. When it’s missing, treat it astrue.
dobYearProvided: false with a placeholder year in dob.Reference: Update customer birth date.New transaction rule REDEEM_REVERSAL
The transactionRule field of a point transaction can now be REDEEM_REVERSAL, recorded when the points from a redeemed reward are returned to the customer. It isn’t counted as newly earned points. The other values are ADJUSTMENT, EARN_RULE, REDEEM_RULE and REFERRAL. It appears in GET /api/external/point-transaction-history/{customer_id} and GET /loyalty/cp/api/transaction-by-shop.Who is affected: clients that map transactionRule to a fixed list of values.Action required: accept values you don’t recognize.Reference: Get customer point transaction history.Customers can’t change a birth date that’s already set
POST /loyalty/cp/api/update-customer-birth-date no longer changes a birth date the customer already has. It returns 400 with the title UserGeneratedError:Birthdate has already been set. Please contact the store to update it. and leaves the birth date as it is. A customer who has no birth date can still set one.Who is affected: storefronts that let signed-in customers change their birthday.Action required: don’t offer customers a way to change a birth date once it’s set. To change one, call PUT /api/external/update-customer-birth-date from your server, which doesn’t have this restriction.Reference: Update customer birth date, Update customer birth date (Admin API).Discount expiry on redeem rules
GET /api/external/point-redeem-rules returns discountExpirationType (DAY, MONTH or YEAR), discountExpirationValue and enableDiscountExpirationOverride on each rule.Birthday and status updates return only the profile
POST /loyalty/cp/api/update-customer-birth-date and POST /loyalty/cp/api/update-customer-status return a smaller customer object. Only the profile fields are filled in: shop, customerId, firstName, lastName, displayName, email, dob, status and loyaltyProgramEnabled. The counters completedActivities, rewardClaimed, referrals and totalRevenue are always 0, and every other field is null, including id, availablePoints, pendingPoints, availableStoreCredits and vipTier. Until now, these calls returned the customer’s full record. The API reference lists more fields, such as totalEarnedPoints, but they’re always null here.Who is affected: storefront code that reads points, store credit or the VIP tier from these responses.Action required: after the call, read availablePoints, storeCreditBalance and currentVipTier from GET /loyalty/cp/api/customer-loyalty.Reference: Get all point redemption rules, Update customer birth date, Update customer loyalty status.Submit a review from an invitation
NewPOST /loyalty/cp/api/submit-review/{reviewToken} submits a product review from a review invitation. reviewToken comes from the invitation, and the body takes rating (1 to 5), title and body. An invalid token returns 400 with the title Invalid review token, and a rating outside 1 to 5 returns 400 with the title Rating must be between 1 and 5.Storefront requests must match the session
The Storefront API now checks thecustomerId and shop you send against the signed-in customer’s session:- A
customerIdin the request body or query string that isn’t the signed-in customer’s returns403with thedetailCustomer does not match the authenticated session. The bodies ofredeem-points,submit-review,update-customer-birth-dateandupdate-customer-statushave acustomerIdfield. - A
shopin the request body that names another store returns403with thedetailShop does not match the authenticated session.submit-reviewis the only endpoint whose body has ashopfield.
customerId or shop, for example in a body copied from the API reference, whose examples still show customerId.Action required: leave customerId and shop out of Storefront API requests. The session already identifies the customer and the store.Reference: Submit an invited review, Submit product review, Redeem customer loyalty points, Update customer birth date, Update customer loyalty status.One error for every failed partner authentication
connect, initiate-connect, verify, disconnect and status under /api/partner/{partnerId}/ now return the same error for every authentication failure: 400 with the errorKey TOKEN_INVALID and the title UserGeneratedError:Invalid partner credentials. That covers a Partner ID that is wrong or belongs to a deactivated partner, a missing or wrong X-Partner-Secret, and an invalid HMAC signature or a timestamp more than 5 minutes off. Until now, an unknown or deactivated Partner ID returned PARTNER_NOT_FOUND, and each authentication failure had its own title, such as UserGeneratedError:Invalid partner secret.Who is affected: partners whose code branches on PARTNER_NOT_FOUND, or on the title of a TOKEN_INVALID error.Action required: stop handling PARTNER_NOT_FOUND and the old titles. On TOKEN_INVALID with the title UserGeneratedError:Invalid partner credentials., check the Partner ID first, then the secret or HMAC key.Reference: Partner integration.Optional daily call allowance per store
The Admin API (/api/external/) can now enforce a daily call allowance for a store. Appstle support sets it per store, and a store without one has no daily cap. Calls made with a partner token don’t count toward it.When a store uses up its allowance, every Admin API call made with its API key returns 429 until 00:00 UTC. The response has a Retry-After header with the number of seconds left until then, and this body (key order can vary):429 responses, for the per-minute rate limit and for too many PUT calls at once, have no Retry-After header, and their body has an error field instead of detail.Who is affected: integrations that make many Admin API calls a day with one store’s API key.Action required: when a 429 has a Retry-After header, stop calling until it elapses, because retrying sooner can’t succeed. Keep exponential backoff for a 429 without one.Reference: Rate limits.Label for already claimed social actions
GET /api/external/widget-labels returns a new field, socialAlreadyClaimedLabel.Reference: List storefront widget label translations.Credit expiry date on point transactions
- Point transactions return
creditExpireAt, the date the store credit from a transaction expires. It’s set only when the store has credit expiry turned on. It appears inGET /api/external/point-transaction-history/{customer_id}andGET /loyalty/cp/api/transaction-by-shop. GET /api/external/widget-labelsreturnsdedicatedPageUnavailableLabel.
Referral errors and a smaller referral history
GETandPOST /loyalty/cp/api/referral-rules/accept-offerreturnerrorKey. When the offer is refused because the friend was already referred, the response is400withstatusFAILEDanderrorKeyALREADY_REFERRED.GET /api/external/widget-labelsreturnsalreadyReferredErrorLabel, the text to show for that error.GET /loyalty/cp/api/customer-referralsreturns less about each referral.referredEmailIdis always masked, sojane@example.comcomes back asj**e@e******.com.description,referralRuleId,referralNote,referredNoteandpixelDataare alwaysnull. Until now, these fields had values, and the email was masked only in some stores.orderCountis listed in the API reference but is alwaysnullon this endpoint.
null, from the referral history.Action required: show referredEmailId as it comes, and build the history from status, createAt, claimAt, referralRewarded and referredRewarded.New body for authentication errors, and shop must match the key
- When the API key or the customer’s storefront session is missing or invalid, the Admin API and the Storefront API return
401with{"status":401,"detail":"Invalid authentication"}. Until now, the body was anapplication/problem+jsondocument with the titleUnauthorized. - An Admin API call with a
shopparameter that names a store other than the API key’s store returns403with thedetailShop does not match the authenticated session. Until now,shopwas ignored.
401 as a credential problem without parsing its body, and leave shop out of Admin API calls, since your API key already identifies the store.Reference: Accept referral offer (POST), Get customer referral history, Error responses.Storefront enrollment no longer accepts a VIP tier
POST /loyalty/cp/api/enroll-customer no longer moves a customer into the VIP tier named in vipTier. A signed-in customer’s request that includes vipTier now returns 400 with the title VIP tier changes require merchant access. Enrolling a customer without vipTier works as before.Stricter checks on reviews and redemptions
POST /loyalty/cp/api/submit-reviewreturns400with the titleRating must be between 1 and 5whenratingis missing or outside 1 to 5. Until now, the rating wasn’t checked.POST /api/external/redeem-pointsandPOST /loyalty/cp/api/redeem-pointsreturn400with the titlePoints to redeem must be greater than zero.when the redeem rule’stypeisDYNAMIC_DISCOUNTandpointsis missing,0or negative. Until now, a missingpointsreturned the titleUserGeneratedError:Please enter the number of points to redeem.- When a redeem rule is limited to customers with certain tags and Appstle can’t read the customer’s tags from Shopify,
redeem-pointsreturns400with the titleUnable to verify reward eligibility. Please try again.Until now, the redemption went ahead without the tag check. The error is temporary.
vipTier when enrolling a customer or submit reviews without a rating, and integrations that redeem dynamic-discount rules.Action required: remove vipTier from the enrollment body. Send a rating from 1 to 5 with every review. Send a positive points value for a dynamic-discount rule, and retry redeem-points after Unable to verify reward eligibility. Please try again.Reference: Enroll customer in loyalty program, Submit product review, Redeem customer points for rewards.Reward Points for Reviews accepts any Product ID
The Reward Points for Reviews action now awards the points when Product ID is empty or isn’t a Shopify product ID, for example when a review app passes a SKU or a handle. Until now, the action failed in those cases and awarded nothing.Who is affected: workflows whose review app passes something other than a Shopify product ID as Product ID. Those actions now succeed.Reference: Reward Points for Reviews.Social media claims say whether points were awarded
POST /loyalty/cp/api/claim-social-media-points now returns a body that says what happened. Until now, it returned 200 with an empty body whether or not the customer earned points. A refused claim still returns 200, so read the body:awarded(boolean):truewhen the points were added.reason(string):AWARDED, or why the claim was refused:CUSTOMER_EXCLUDED,RULE_NOT_FOUND,RULE_OUTSIDE_SCHEDULE,CUSTOMER_TAGS_UNAVAILABLE,RESTRICTED_CUSTOMER_TAG,MISSING_ALLOWED_CUSTOMER_TAG,IMPORTED_CUSTOMER,ALREADY_CLAIMEDorNOT_ELIGIBLE.message(string): a short description of the outcome.points(number): the points added. It’snullwhen the claim is refused.
Extra points for review photos and videos
GET /api/external/point-earn-rules returns addAdditionalPhotoPoints, additionalPhotoPoints, addAdditionalVideoPoints and additionalVideoPoints on review earn rules.Reference: Get all point earning rules.