Crystallize logo

Set Promotions

You can now set Promotions in Crystallize via the Shop API. Learn more about what a promotion entails. Note that you can also set up promotions in the Crystallize App itself.

note

Endpoint and scope

All operations on this page run on the /{tenant}/cart/admin endpoint and require an access token with the cart:admin scope.

Example Mutation

Let’s start off with looking at a mutation, followed by going deeper into each of the fields in the mutation:

mutation CreatePromotion {
  setPromotions(
    input: [
      {
        identifier: "summer_sale_2024"
        name: "Summer Sale 2024"
        description: "Get amazing discounts on summer essentials!"
        periods: [
          { start: "2024-06-01T00:00:00Z", end: "2024-08-31T23:59:59Z" }
        ]
        triggers: {
          minValue: 50.00
          customerGroups: ["crystal_customers"]
        }
        targets: {
          topics: ["summer_wear"]
          exclusions: { skus: ["beach-shorts-blue", "beach-shorts-white"] }
        }
        limitations: {
          maxUsage: 1000
          maxUsagePerCustomer: 2
          cumulativeDiscount: false
          repeatable: true
        }
        mechanism: {
          type: Percentage
          value: 20.0
        }
      }
    ]
  ) {
    identifier
    name
    description
    # Add other fields you want to return after creation
  }
}

The setPromotions mutation takes a list of promotions, saves the full list, and returns it. It is an override: any existing promotion that is not included in the input is removed.

Identifier

Required field to specify the identifier of the promotion in Crystallize.

Name

This is an optional field for providing the name of the promotion.

Description

Another optional field to add a description for your promotion.

Periods

An array of periods a promotion should be valid for. Each period has two fields:

  • start: A date-time string marking when the period begins.
  • end: A date-time string marking when the period ends.

If no periods are specified, the promotion takes effect immediately and is permanent until modified or deleted.

Mechanism

A mechanism specifies the type of discount you would like to apply. It takes two values: type and value. Value is always a floating point number, and type can be one of the following:

  • Percentage: A percentage value.
  • Fixed: A fixed amount as a discount.
  • DynamicFixed: For when you want the discount amount to be based on a new set price.
  • XforY: Buy X for the price of Y. If items in the cart have different prices, then the cheapest one(s) will be discounted.

For an XforY promotion, set the mechanism value to 0 and provide the X and Y quantities in the triggers.xForY field (see Triggers).

Triggers

This is a list of conditions that will trigger the promotion to be applied to a cart. It can be one or more of the following:

  • skus: An array of stock-keeping units (SKUs).
  • topics: If an item contains any of the topics provided to this array.
  • paths: An array of item paths.
  • productIds: An array of product IDs.
  • minValue: A float value for the minimum value of the cart.
  • minQuantity: This is an integer you can provide to specify the minimum quantity in the cart.
  • currencies: An array of currencies. For example, if you have a multi-currency shop, only trigger the promotion for Euros or Dollars.
  • markets: You can create specific segments in Crystallize that are called markets. This field takes an array of market identifiers.
  • customers: This is for when you want to trigger a promotion based on the customer. It takes a list of customer identifiers.
  • customerGroups: You can have customer groups assigned to your customers. This field takes a list of customer groups. Example: when you have customer levels based on loyalty points earned.
  • codes: An array of codes that, if present in the cart, would trigger the discount. Use this for voucher-based discounts.
  • xForY: An array of two integers, [X, Y], for "Buy X, pay for Y" deals. Used together with the XforY mechanism.
  • exclusions: A set of conditions that exclude items or carts from the promotion. It accepts the same fields as triggers (skus, topics, paths, productIds, minValue, minQuantity, currencies, codes, markets, customers, customerGroups).

Targets

You have triggers to specify the conditions for when a promotion is applied. Targets define which item(s) the promotion is applied to. Targets share some fields with triggers:

  • skus
  • paths
  • productIds
  • topics
  • exclusions: Excludes items from the target set. Accepts skus, paths, productIds, and topics.

Targets are not used with the XforY mechanism, which discounts the cheapest qualifying item(s) automatically.

Limitations

You might want to add some limitations to your promotions, and that is exactly what the limitations field does. You can set the following limitations:

  • canBeCombinedWith: An array of other promotion identifiers this promotion can be combined with.
  • maxUsage: An integer value stating how many times the promotion can be applied in total.
  • maxUsagePerCustomer: Similar to maxUsage, but defines the maximum usage per customer.
  • cumulativeDiscount: A boolean value to decide whether the discount can be applied in addition to other discounts.
  • quantityPerTrigger: An integer value to specify the number of times a discount can be applied per trigger.
  • repeatable: Takes a boolean value to determine whether the discount should be repeated in the cart, or applied only once.

Querying promotions

Once created, promotions can be retrieved and inspected with the following admin queries:

  • promotion: Fetch a single promotion by its identifier.
  • promotions: List all promotions configured on the tenant.
  • promotionUsage: Inspect how often promotions have been applied. It exposes count (total usage) and perCustomer (usage per customer), each of which can optionally be filtered by a promotion identifier.
query Promotions {
  promotions {
    identifier
    name
    mechanism { type value }
  }
  promotion(identifier: "summer_sale_2024") {
    identifier
    name
    periods { start end }
  }
  promotionUsage {
    count(identifier: "summer_sale_2024")
    perCustomer(identifier: "summer_sale_2024")
  }
}

Promotion Price Interface (PPI)

The Promotion Price Interface (PPI) lets you delegate promotion pricing to an external endpoint. Crystallize calls the configured URL to resolve promotion prices, optionally refreshing them on a schedule.

Configure it with the setPPI mutation:

  • url (required): The external endpoint Crystallize calls to resolve promotion prices.
  • refreshFrequency (optional): The refresh frequency in hours. Defaults to 0, meaning the data is never refreshed. When set, it must be at least 2 hours.

Retrieve the current configuration with the ppi query.

mutation ConfigurePPI {
  setPPI(url: "https://example.com/ppi", refreshFrequency: 24) {
    url
    refreshFrequency
  }
}

query GetPPI {
  ppi {
    url
    refreshFrequency
  }
}

Cart administration

The cart admin endpoint also lets you list and filter carts across your tenant — useful for analytics and for finding abandoned carts. The carts query returns the matching carts together with a summary that includes totals and aggregates.

You can filter and paginate carts with the following arguments:

  • limit / skip: Pagination (default limit 100, skip 0).
  • sortBy: Sort by updatedAt, createdAt, staleAt, expiresAt, or gross, each ASC or DESC (defaults to updatedAt DESC).
  • createdAfter / createdBefore / updatedAfter / updatedBefore: Date-range filters.
  • grossGreaterThan / grossLessThan: Filter by cart total.
  • states: Filter by cart state — cart, placed, ordered, or abandoned. Defaults to [cart], so only active carts are returned unless you specify other states.
  • types: Filter by cart type (cart or wishlist).
  • expired / stale: Filter by expiration or staleness.
  • name / customerIdentifier / currency: Filter by cart name, customer, or currency.
  • includePerTimeAggregates: A boolean that, when true, adds per-state and per-time-span breakdowns to the summary aggregates.

Abandoned carts are not a separate query — abandonment is a cart state. To list them, filter by state with states: [abandoned].

query AbandonedCarts {
  carts(
    states: [abandoned]
    limit: 50
    sortBy: { updatedAt: DESC }
  ) {
    summary {
      totalHits
      totalGross
    }
    hits {
      id
      state
      updatedAt
    }
  }
}

Check out our promotions livestream

Watch our deep dive on building and configuring promotions with the Shop API.

;