Configure a loyalty season

Creates or updates one loyalty campaign (season) together with the shop-level display settings, bonus definitions and reward products it depends on, optionally sets or removes the program-level spend conversion table, and optionally activates the program. The campaign id is the upsert key: an existing id updates that season, a new id creates one. The request describes the whole season it names, so a tier or milestone omitted from it is removed from that season; bonuses and reward products are created or updated by id or SKU and never removed. Draft and scheduled seasons accept every change. An active season accepts names, descriptions, images, milestone rewards, tier earn rates and a new end date; thresholds, adding or removing tiers or milestones, the start date and reset rules are refused with FAILED_PRECONDITION listing the fields. Ended seasons are read-only. This method accepts only the versioned x-stash-hmac-signature header, signed over the request body exactly as sent; the legacy stash-hmac-signature header is rejected with HTTP 401 (UNAUTHENTICATED), because it carries no timestamp and a captured request could be replayed. Applying is idempotent, so resending the same request is safe. Errors, by the HTTP status a REST caller receives and its gRPC status. Two of them share HTTP 400; a REST caller tells them apart by the code in the error body, 3 for INVALID_ARGUMENT and 9 for FAILED_PRECONDITION. 400 INVALID_ARGUMENT (code 3) with per-field violations when validation fails; fix the request, because resending it unchanged does not help. 400 FAILED_PRECONDITION (code 9) when the request cannot apply to the program as it stands, with the reason in the message: for example a locked field of an active season changes (the message lists the fields), the shop has no loyalty program and activate is not set, or the shop's loyalty program is bound to more than one shop; resend only once that changes, or as is when the message says to re-send the request. 401 UNAUTHENTICATED when the signature is missing, invalid, outside the time window, sent in the legacy header, or names a different shop; fix the signing. 403 PERMISSION_DENIED when the shop is not enabled for configuration writes; it does not change on a retry. 404 NOT_FOUND for an unknown shop; it does not change on a retry. 409 ABORTED when another update for the shop is in progress; resend the same request after a short wait, which is safe because applying is idempotent. 501 UNIMPLEMENTED when the loyalty configuration API is not available; it does not change on a retry. 500 INTERNAL when Stash hits an internal error, for example reading or writing the shop's loyalty configuration; resend the same request with backoff.

PUT
/sdk/studio/{shopId}/loyalty/config
x-stash-hmac-signature<token>

Versioned HMAC-SHA256 signature authenticating server-to-server SDK requests (your backend calling Stash), as opposed to client-side SDK operations. Recommended over the deprecated X-Stash-Api-Key for the best security. Header format: v1;{appId};{unixMillisTimestamp};{base64Signature} (semicolon-delimited). {base64Signature} is the base64-encoded HMAC-SHA256 of the string {unixMillisTimestamp}.{requestBody} using your app ingress secret (the ingress secret is shown base64-encoded in Studio; base64-decode it to the raw bytes used as the HMAC key), where {requestBody} is the request body exactly as you send it for POST and PUT requests (the compact canonical request JSON is also accepted) and empty for GET requests. A signature over the exact bytes verifies for bodies of up to 18 KiB (18,432 bytes); a larger body may be refused with 401 even when correctly signed. The timestamp must be within 5 minutes of Stash server time. Your appId is your immutable app identifier, shown in Stash Studio under Project Settings > App details; your ingress secret is under Project Settings > API Secrets.

In: header

Path Parameters

shopId*string

Shop identifier from Stash Studio

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

One season plus the shop-level items it depends on, and optionally the program-level spend conversion table. Signed over the request body.

Response Body

application/json

application/json

curl -X PUT "https://example.com/sdk/studio/string/loyalty/config" \  -H "Content-Type: application/json" \  -d '{    "campaign": {      "id": "string",      "name": "string"    }  }'
{  "displayConfig": {    "programName": "string",    "vipStoreName": "string"  },  "bonuses": [    {      "id": "string",      "type": "BONUS_TYPE_UNSPECIFIED",      "name": "string",      "pluralName": "string",      "abbreviatedName": "string",      "iconImageUrl": "string"    }  ],  "products": [    {      "sku": "string",      "name": "string",      "description": "string",      "imageUrl": "string",      "backgroundImageUrl": "string",      "status": "REWARD_PRODUCT_STATUS_UNSPECIFIED"    }  ],  "campaign": {    "id": "string",    "name": "string",    "startAt": "2019-08-24T14:15:22Z",    "endAt": "2019-08-24T14:15:22Z",    "reset": {      "points": "POINTS_UNSPECIFIED",      "milestoneClaims": "MILESTONE_CLAIMS_UNSPECIFIED",      "currency": "CURRENCY_UNSPECIFIED"    },    "tiers": [      {        "id": "string",        "name": "string",        "startPoints": 0,        "iconUrl": "string",        "backgroundImageUrl": "string",        "color": "string",        "benefits": [          {            "text": "string"          }        ],        "earnRates": [          {            "bonusId": "string",            "rewardPerDollar": 0          }        ],        "milestones": [          {            "id": "string",            "name": "string",            "description": "string",            "pointsNeeded": 0,            "rewardName": "string",            "rewardImageUrl": "string",            "backgroundImageUrl": "string",            "bonusRewards": [              {                "bonusId": "string",                "amount": 0              }            ],            "productRewards": [              {                "sku": "string",                "quantity": 0              }            ]          }        ]      }    ],    "status": "CAMPAIGN_STATUS_UNSPECIFIED"  },  "warnings": [    "string"  ],  "spendConversion": {    "tiers": [      {        "prices": {          "property1": "string",          "property2": "string"        }      }    ],    "ratesPerUsd": {      "property1": "string",      "property2": "string"    },    "updatedAt": "2019-08-24T14:15:22Z"  }}

How is this guide?