Skip to content

Promo codes and seller promotions

Buyers enter promo codes on a cart. A code is either a seller promotion (a selling profile’s own offer, on its own listings only) or a United Pet Club promotion. United Pet Club works out every amount: a client never sends a discount amount, and one that tries is refused.

All amounts named *InBase or amount are decimal amounts in the cart’s (the seller’s) currency; fields ending in Minor are minor units of the currencyCode beside them. Rates are basis points (200 = 2 %).

POST /api/cart/v1/carts/{cartId}/promo-codes
Authorization: Bearer <member token>
Content-Type: application/json
{ "code": "SPRING-GROOM" }
{
"data": {
"cart": { "id": "…", "discounts": [ … ], "summary": { … } },
"rejected": [
{ "code": "WELCOME5", "reasonCode": "REPLACED", "message": "Another code from the same seller replaced this one." }
]
}
}

(Other cart fields omitted.)

  • The answer is the whole cart after United Pet Club re-priced all of its codes, plus the codes that did not apply. A refused code is not an error: the call succeeds with the code in rejected.
  • Codes are trimmed and compared without regard to case.
  • Remove a code with DELETE /api/cart/v1/carts/{cartId}/promo-codes/{code}; same answer shape.
  • Changing the cart’s lines re-prices its codes too; a code that no longer fits drops off.
  • 503 with the retryable platform response code Cart_Shopping_GT_OfferCheck_FL means the codes could not be checked right now. The cart is unchanged; retry.
  • 403 Cart_Shopping_VL_Authorization_RJ: the caller is not the cart’s owner acting as that profile.
  • POST /api/cart/v1/carts/{cartId}/discounts no longer accepts a promotion, a promotion id or a coupon code from a member (403); use the promo-code routes.

Each applied code becomes one discount per cart line it reaches. The cart detail’s discounts[] entries for a promo code carry:

FieldMeaning
couponCodeSnapshotthe code as the buyer entered it — group the entries by it to show each code once
cartLineIdthe line it lowers
amountInBasehow much it takes off that line
promotionIdLogical, promoRedemptionIdLogicalthe promotion, and this cart’s use of it
fundedBySELLER (a seller promotion: label it with the seller’s name) or UPC (a United Pet Club promotion)
fundedByProfileIdLogicalthe profile that funds it

Show each code once with its total saving and a way to remove it. Taxes are calculated on the discounted lines.

  • At most one code per seller and one United Pet Club code per cart, five codes in all. A second code of the same seller replaces the first (REPLACED).
  • Seller promotions apply first, each to that seller’s own lines; the United Pet Club promotion then applies to what is left. A line never goes below zero.
  • A promotion that does not combine refuses to share a line with another code (NOT_STACKABLE).
  • No promotion applies to donations or gift cards; a seller promotion never applies to another seller’s items or to United Pet Club plans, add-ons or fees.
  • A code counts as used when the order is paid, not when it is applied.
reasonCodeMeaning
CODE_INVALIDno such code, or it is not the caller’s (reward codes work only for the profile they were issued to)
CODE_EXPIREDthe code has expired
LIMIT_REACHEDthe code or promotion was used as many times as it allows, or the cart already holds five codes
NOT_ACTIVEthe promotion is not running (paused, ended, outside its dates)
NOT_ELIGIBLEthe buyer does not meet the promotion’s conditions
RISK_BLOCKEDrefused by fraud checks
NO_ELIGIBLE_ITEMSnothing in the cart can take it — for a seller promotion: none of that seller’s own items
MIN_CART_NOT_METthe eligible items do not reach the promotion’s minimum
CURRENCY_MISMATCHan amount-off code in another currency than the cart
NOT_STACKABLEit cannot share its items with a code already applied
REPLACEDanother code from the same seller replaced it
NOT_STARTEDthe promotion is scheduled and has not started yet; message names the start date
ENDEDthe promotion’s end date has passed
OUT_OF_REGIONthe promotion is limited to a region, and the order is not going there
REGION_UNKNOWNthe promotion is limited to a region, and neither the order nor the buyer’s profile has an address yet

Show message to the buyer, or your own text per reasonCode.

Where an order goes. A regional promotion checks where the purchase happens, never the buyer’s IP address: for shipped goods the cart’s shipping address, for a booking the place the service is performed, otherwise the address on the buyer’s profile. Set the shipping address before checkout and a regional code is checked against it.

GET /api/promotion/v1/me/reward-codes lists the referral reward codes issued to the acting profile:

{
"data": [
{ "codeId": "…", "code": "R-7KQ2M9XA", "promoId": "…", "promoTitle": "Refer a friend", "offerSummaryText": "…",
"ownerKindCode": "THIRD_PARTY", "ownerProfileId": "…", "rewardSourceCode": "REFERRAL", "sourceRoleCode": "REFERRER",
"isUsed": false, "expiresAtUtc": null, "issuedAtUtc": "2026-10-15T18:02:11Z" }
]
}

A reward code works once, only for that profile. ownerKindCode THIRD_PARTY means it is a seller’s reward and applies to that seller’s own items only; FIRST_PARTY means it is United Pet Club’s.

A profile runs its own promotions when it is an approved commerce seller, a verified organization or a verified shelter or rescue. Running one is free; each time a buyer uses it on a paid order, the profile pays a per-claim fee (default 2 %, returned claimFeeRateBps) of the claimed lines — the items the promotion lowered — after its discount and before tax (no other items, no United Pet Club discount, no tax, levies, deposits or shipping), in the order’s currency, taken from its earnings like the Commission and returned in proportion on refunds.

All routes are under /api/promotion/v1/seller/ and act for the profile the caller is acting as. Anyone else gets 404 for a promotion that is not theirs.

RouteDoes
GET eligibility{ allowed, known, basis, claimFeeRateBps }; basis is COMMERCE_SELLER, VERIFIED_ORGANIZATION or VERIFIED_SHELTER_RESCUE; known: false = could not check, retry
GET promosthe profile’s promotions, newest first
POST promoscreate (201); starts as a draft with one code
GET promos/{id}one promotion
PUT promos/{id}edit: the draft in place, or a new draft version while the live one keeps running
POST promos/{id}/publishthe newest draft goes live; existing codes follow it
POST promos/{id}/pause, POST promos/{id}/resumestop and restart its codes
POST promos/{id}/endend it for good; its codes stop working
POST promos/{id}/codesanother code: { "code"?, "maxRedemptions"?, "expiresAtUtc"? }
GET promos/{id}/stats{ promoId, applied, claims, byCurrency: [{ currencyCode, claims, discountMinor, claimedSalesMinor, feeMinor, reversedFeeMinor }] }
GET claims?promoId=the paid orders that used the profile’s promotions or referral rewards, with the fee of each
POST /api/promotion/v1/seller/promos
Authorization: Bearer <member token, acting as the selling profile>
Content-Type: application/json
{
"title": "Spring grooming",
"offerSummaryText": "A percentage off any grooming visit",
"discountKindCode": "PERCENT_OFF",
"percentValue": 10,
"scopeCode": "ALL_MY_LISTINGS",
"endsAtUtc": "2026-11-30T07:59:59Z",
"maxRedemptionsPerCustomer": 1,
"code": "SPRING-GROOM"
}
FieldRules
titlerequired, up to 200 characters
shortDescription, offerSummaryTextoptional; up to 500 and 300 characters. offerSummaryText is shown to buyers
discountKindCodePERCENT_OFF (percentValue 1–100) or FIXED_OFF (amount > 0 and a 3-letter currencyCode: an amount off, the “store credit” reward)
scopeCodeALL_MY_LISTINGS, or ONE_LISTING with listingId and listingKindCode
listingKindCodefor ONE_LISTING: OFFER (default — a store offer: products, bundles, classes sold as offers), SERVICE (a bookable service or group class) or MEMBERSHIP_TIER (one of the profile’s own organization’s membership tiers: its dues). The listing must be one the profile sells now; United Pet Club’s own tiers are never the profile’s
duesTermsCountoptional, 1–36, default 1: how many membership dues terms the discount covers, counting the first payment (a member who joins with the code pays the discounted dues for that many terms, automatic renewals included). Above 1 only for MEMBERSHIP_TIER or ALL_MY_LISTINGS
minCartAmount, maxDiscountAmountoptional; minimum ≥ 0, maximum > 0
startsAtUtc, endsAtUtcoptional; the end after the start
maxTotalRedemptions, maxRedemptionsPerCustomeroptional; at least 1
codeoptional; 4–32 characters A–Z 0–9 -, starting with a letter or digit, unique across United Pet Club. Generated when empty
isStackableoptional, default true: may combine with a United Pet Club promotion on the same item
regionoptional; null = everywhere. { "mode": "INCLUDE", "regions": [{ "countryCode": "CA", "subdivisionCodes": ["CA-ON"] }] }: ISO 3166-1 countries, optional ISO 3166-2 provinces or states (an empty list = the whole country). A profile’s promotion is INCLUDE only and must lie inside where the profile sells (GET /api/promotion/v1/seller/sales-area)
timeZoneIanaoptional; the time zone your editor shows the dates in, such as America/Toronto. The dates themselves are always UTC
localeCodeoptional, default en-US

A seller promotion can only carry this one discount: no other reward kinds, no scopes beyond the profile’s own listings, no audiences or rate limits. A promotion only ever takes money off the owner’s own lines at checkout. Create, update, publish and resume check that a one-listing promotion’s listing is still the profile’s.

Schedule ahead. Publish with a future startsAtUtc and the promotion is SCHEDULED: its codes exist and can be handed out, but checkout refuses them with NOT_STARTED until the start. It becomes ACTIVE at the start and ENDED after endsAtUtc on its own. A code never outlives the promotion: publishing sets every later (or open-ended) code expiry to the promotion’s end, and adding a code that expires later is refused.

GET /api/promotion/v1/seller/sales-area
Authorization: Bearer <member token, acting as the selling profile>

answers the region the profile’s promotions may cover, in the same region shape: where its store offers can be bought, where its services are performed, and its own address.

Bookings take the provider’s own codes at booking time (the discount lowers the booking’s price); United Pet Club codes apply to store carts only.

Membership dues. An organization’s code applies when a member joins or changes tier, not in a cart: send it as promoCode on POST /api/membership/v1/applications or POST /api/membership/v1/memberships/{id}/change-requests (TIER_CHANGE or RENEW). The application / change request then quotes the discounted dues (feeAmountSnapshot / quotedAmount) with promoCode, promoDiscountAmount (per term) and promoDuesTermsCount. A code that can’t be used answers 400 (Membership_Enrollment_VL_Request_RJ); 503 (Membership_Enrollment_HD_Promotion_FL) means it could not be checked, retry. Nothing is filed either way. Each discounted term the member pays is a claim with its own fee.

{ id, ownerProfileId, ownerKindCode: "THIRD_PARTY", slug, title, shortDescription, offerSummaryText,
statusCode, versionStatusCode, versionId, currentPublishedVersionId, isAdminHeld,
discountKindCode, percentValue, amount, currencyCode, scopeCode, listingId, listingKindCode, minCartAmount, maxDiscountAmount,
startsAtUtc, endsAtUtc, maxTotalRedemptions, maxRedemptionsPerCustomer, isStackable,
codes: [{ id, code, uses, maxRedemptions, expiresAtUtc, isRevoked }], claimsCount, createdAtUtc, updatedAtUtc,
region, timeZoneIana }

statusCode is DRAFT, SCHEDULED (published, not started), ACTIVE, PAUSED or ENDED (past its end, or ended). versionId is the newest version (a draft while an edit waits to be published); currentPublishedVersionId is the one buyers use now. isAdminHeld: true means United Pet Club paused it: the profile cannot resume or publish it until United Pet Club resumes it.

{ id, promoId, promoTitle, orderId, ownerProfileId, rewardSourceCode: "PROMOTION" | "REFERRAL", sourceReferenceId,
currencyCode, claimBaseMinor, discountMinor, feeRateBps, feeMinor, reversedFeeMinor,
statusCode: "ASSESSED" | "PARTIALLY_REVERSED" | "REVERSED", assessedAtUtc,
claimSourceKindCode: "ORDER" | "MEMBERSHIP_DUES", termNumber }

claimBaseMinor is what the fee was calculated on (the claimed lines after the seller’s discount); reversedFeeMinor is what refunds gave back. For MEMBERSHIP_DUES claims, orderId is the membership application or change request the code was priced on and termNumber the dues term (1 = the first payment, 2+ = automatic renewals); one claim per discounted term.

HTTPPlatform response codeWhen
400Promotion_Redemption_VL_Request_RJa field breaks the rules above, or a code would expire after the promotion ends (promotion.code_expiry_after_end)
403Promotion_Redemption_VL_Authorization_RJthe profile may not run promotions, the caller acts as no profile, the listing is not the profile’s (promotion.listing_not_owned), or the region reaches beyond where the profile sells (promotion.region_outside_sales_area)
404Promotion_Redemption_GT_Resource_FLnot a promotion of the acting profile
409Promotion_Redemption_AD_Duplicate_RJthe code is taken
409Promotion_Redemption_UP_Concurrency_RJeligibility, the listing’s owner or where the profile sells (promotion.sales_area_unknown) could not be checked right now (retryable)
409Promotion_Redemption_UP_Transition_RJthe action does not fit the promotion’s state (for example resuming one that is not paused), or United Pet Club holds it (promotion.admin_hold)

Create, publish and resume check the profile’s eligibility again; the body’s error code tells the 403 and 409 causes apart.