Install the Discounts Backend API

Edit on GitHub

This document describes how to install the Discounts Backend API, which exposes discounts at /discounts, their voucher codes at /discounts/{uuid}/voucher-codes, and the values a discount is configured from at /discount-options through the Glue Backend application. For the endpoint reference, see Backend API: Manage discounts, Backend API: Manage discount voucher codes, and Backend API: Retrieve discount options.

Prerequisites

Install the required features:

NAME VERSION INSTALLATION GUIDE
Spryker Core 202608.0 Install the Spryker Core feature
Promotions & Discounts 202608.0 Install the Promotions & Discounts feature
API Platform — Enable API Platform
API Platform is required

The /discounts, /discount-voucher-codes, and /discount-options resources are generated by API Platform, which is not enabled in every project. If your project does not serve any API Platform resource on the Glue Backend application yet, complete Enable API Platform first—this guide assumes the Glue Backend application already boots with SprykerApiPlatformBundle registered.

Install feature core

1) Install the required modules

Install the required modules using Composer:

composer require \
  spryker/api-platform:"^1.51.0" \
  spryker/discount:"^9.57.0" \
  spryker/discount-extension:"^1.4.0" \
  spryker/discount-promotion:"^4.17.0" \
  spryker/uuid-behavior:"^1.0.0" \
  --update-with-dependencies
What each version provides
MODULE MINIMUM VERSION PROVIDES
spryker/api-platform ^1.51.0 The collection query reader the discount resources read their filter, sort, and pagination parameters with, and the resource links the discounts resource declares. Older versions do not boot the discount providers, so the version is pinned here even if the package is already installed
spryker/discount ^9.57.0 The discounts, discount-voucher-codes, discount-voucher-codes-generate, discount-voucher-codes-export, and discount-options resources, the uuid column of spy_discount, and the business rules the resources validate with
spryker/discount-extension ^1.4.0 The plugin interfaces other modules use to extend the discounts the API reads, the discount options, and the validation
spryker/discount-promotion ^4.17.0 The promotion attribute of the discounts resource and the promotion item-selection strategy, wired through the Glue configuration of step 3 and the plugins of step 6. Optional: install it only if your project uses promotional products
spryker/uuid-behavior ^1.0.0 Generation of the discount UUIDs
Verification

Make sure the following modules have been installed:

MODULE EXPECTED DIRECTORY
Discount vendor/spryker/discount
DiscountExtension vendor/spryker/discount-extension
DiscountPromotion vendor/spryker/discount-promotion
UuidBehavior vendor/spryker/uuid-behavior

2) Check the API Platform configuration

The resource schemas ship inside the installed packages, at vendor/spryker/discount/resources/api/backend/*.resource.yml and vendor/spryker/discount-promotion/resources/api/backend/discounts.resource.yml. The generator finds them as long as the Glue Backend application serves the backend API type and scans the directory the modules are installed into.

In config/GlueBackend/packages/spryker_api_platform.php, confirm that apiTypes() includes backend. Leave sourceDirectories() alone unless your project overrides it: the default is vendor/spryker and src/Pyz, which covers the installed packages. For what both settings do, see Configuration.

Projects that override sourceDirectories

If your project sets sourceDirectories() explicitly, add the directory the modules are installed into rather than replacing the list. Dropping the other entries hides every resource your project already serves.

3) Set up configuration

Discounts are addressed by UUID, which is an optional column of spy_discount. Enable it:

src/Pyz/Zed/Discount/DiscountConfig.php

<?php

namespace Pyz\Zed\Discount;

use Spryker\Zed\Discount\DiscountConfig as SprykerDiscountConfig;

class DiscountConfig extends SprykerDiscountConfig
{
    public function isDiscountUuidEnabled(): bool
    {
        return true;
    }
}
The UUID must be enabled

Every discount resource resolves its discount by UUID. With isDiscountUuidEnabled() left at false, every discount request fails with a generic 500 instead of returning a wrong discount; the exception logged by the Glue Backend application names this setting.

If the DiscountPromotion module is installed, wire its promotion attribute in the Glue configuration; skip this if the module is not installed. getTransferPathByAttribute() tells the discounts resource where the attribute is stored on the discount, getAttributeByEntityIdentifier() makes validation errors name promotion.abstractSkus and promotion.quantity, and getResponseCodeByErrorMessage() reports them with the 5730 code instead of the generic 5799.

src/Pyz/Glue/Discount/DiscountConfig.php

<?php

namespace Pyz\Glue\Discount;

use Spryker\Glue\Discount\DiscountConfig as SprykerDiscountConfig;
use Spryker\Shared\DiscountPromotion\DiscountPromotionConfig;

class DiscountConfig extends SprykerDiscountConfig
{
    protected const string ATTRIBUTE_PROMOTION = 'promotion';

    protected const string TRANSFER_PATH_DISCOUNT_PROMOTION = 'discountCalculator.discountPromotion';

    protected const string ATTRIBUTE_PROMOTION_ABSTRACT_SKUS = 'promotion.abstractSkus';

    protected const string ATTRIBUTE_PROMOTION_QUANTITY = 'promotion.quantity';

    /**
     * @uses \Spryker\Zed\DiscountPromotion\Business\Validator\Rule\DiscountPromotion\AbstractSkusDiscountPromotionValidatorRule::ENTITY_IDENTIFIER_ABSTRACT_SKUS
     */
    protected const string ENTITY_IDENTIFIER_PROMOTION_ABSTRACT_SKUS = 'discountCalculator.discountPromotion.abstractSkus';

    /**
     * @uses \Spryker\Zed\DiscountPromotion\Business\Validator\Rule\DiscountPromotion\QuantityDiscountPromotionValidatorRule::ENTITY_IDENTIFIER_QUANTITY
     */
    protected const string ENTITY_IDENTIFIER_PROMOTION_QUANTITY = 'discountCalculator.discountPromotion.quantity';

    /**
     * @return array<string, string>
     */
    public function getTransferPathByAttribute(): array
    {
        return parent::getTransferPathByAttribute() + [
            static::ATTRIBUTE_PROMOTION => static::TRANSFER_PATH_DISCOUNT_PROMOTION,
        ];
    }

    /**
     * @return array<string, string>
     */
    public function getAttributeByEntityIdentifier(): array
    {
        return parent::getAttributeByEntityIdentifier() + [
            static::ENTITY_IDENTIFIER_PROMOTION_ABSTRACT_SKUS => static::ATTRIBUTE_PROMOTION_ABSTRACT_SKUS,
            static::ENTITY_IDENTIFIER_PROMOTION_QUANTITY => static::ATTRIBUTE_PROMOTION_QUANTITY,
        ];
    }

    /**
     * @return array<string, string>
     */
    public function getResponseCodeByErrorMessage(): array
    {
        return parent::getResponseCodeByErrorMessage() + [
            DiscountPromotionConfig::TRANSLATION_KEY_ABSTRACT_SKUS_REQUIRED => static::RESPONSE_CODE_PROMOTION_INVALID,
            DiscountPromotionConfig::TRANSLATION_KEY_QUANTITY_INVALID => static::RESPONSE_CODE_PROMOTION_INVALID,
            DiscountPromotionConfig::TRANSLATION_KEY_ABSTRACT_SKUS_UNKNOWN => static::RESPONSE_CODE_PROMOTION_INVALID,
        ];
    }
}

4) Set up the database schema and transfer objects

Apply the database schema changes and generate the transfer objects:

console transfer:generate
console propel:install
Verification

Make sure the uuid column has been added to the spy_discount table, and that the following transfers have been created:

TRANSFER TYPE EVENT PATH
DiscountCriteria class created src/Generated/Shared/Transfer/DiscountCriteriaTransfer.php
DiscountConditions class created src/Generated/Shared/Transfer/DiscountConditionsTransfer.php
DiscountConfiguratorCollection class created src/Generated/Shared/Transfer/DiscountConfiguratorCollectionTransfer.php
DiscountConfigurationOptions class created src/Generated/Shared/Transfer/DiscountConfigurationOptionsTransfer.php
DiscountCalculatorPluginOption class created src/Generated/Shared/Transfer/DiscountCalculatorPluginOptionTransfer.php
DiscountCollectorStrategyOption class created src/Generated/Shared/Transfer/DiscountCollectorStrategyOptionTransfer.php
DiscountIntegerRange class created src/Generated/Shared/Transfer/DiscountIntegerRangeTransfer.php
DiscountRuleFieldOption class created src/Generated/Shared/Transfer/DiscountRuleFieldOptionTransfer.php
DiscountRuleFieldValueOption class created src/Generated/Shared/Transfer/DiscountRuleFieldValueOptionTransfer.php
DiscountRuleGroup class created src/Generated/Shared/Transfer/DiscountRuleGroupTransfer.php
DiscountRule class created src/Generated/Shared/Transfer/DiscountRuleTransfer.php
DiscountRuleGroupResponse class created src/Generated/Shared/Transfer/DiscountRuleGroupResponseTransfer.php
DiscountVoucherCriteria class created src/Generated/Shared/Transfer/DiscountVoucherCriteriaTransfer.php
DiscountVoucherConditions class created src/Generated/Shared/Transfer/DiscountVoucherConditionsTransfer.php
DiscountVoucherCollection class created src/Generated/Shared/Transfer/DiscountVoucherCollectionTransfer.php
DiscountVoucherCollectionDeleteCriteria class created src/Generated/Shared/Transfer/DiscountVoucherCollectionDeleteCriteriaTransfer.php
DiscountVoucherCollectionResponse class created src/Generated/Shared/Transfer/DiscountVoucherCollectionResponseTransfer.php
DiscountVoucherResponse class created src/Generated/Shared/Transfer/DiscountVoucherResponseTransfer.php
Discount.uuid property created src/Generated/Shared/Transfer/DiscountTransfer.php
DiscountGeneral.uuid, createdAt property created src/Generated/Shared/Transfer/DiscountGeneralTransfer.php
DiscountVoucher.idDiscountVoucher, isActive, createdAt property created src/Generated/Shared/Transfer/DiscountVoucherTransfer.php
DiscountConfiguratorResponse.errors property created src/Generated/Shared/Transfer/DiscountConfiguratorResponseTransfer.php
VoucherCreateInfo.codes property created src/Generated/Shared/Transfer/VoucherCreateInfoTransfer.php
MoneyValue.fkCurrency property created src/Generated/Shared/Transfer/MoneyValueTransfer.php
Currency.idCurrency property created src/Generated/Shared/Transfer/CurrencyTransfer.php
Store.availableCurrencyIsoCodes property created src/Generated/Shared/Transfer/StoreTransfer.php

5) Generate the discount UUIDs

The UUID behavior fills uuid when a discount is saved, so discounts that existed before the column was added have an empty uuid and cannot be addressed by the API. Generate the missing values:

console uuid:generate Discount spy_discount
Verification

Make sure every row has a UUID:

select count(*) from spy_discount where uuid is NULL;

The result must be 0.

6) Set up behavior

The DiscountPromotion module contributes the promotion strategy through plugins. Register them; skip this step if the module is not installed.

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
DiscountPromotionConfigurationOptionsExpanderPlugin Adds the promotion strategy to collectorStrategyTypes of the discount-options resource. Spryker\Zed\DiscountPromotion\Communication\Plugin\Discount
DiscountPromotionDiscountConfiguratorValidatorRulePlugin Validates promotion of a discount the API creates or updates with the promotion strategy: at least one existing abstract product SKU and a quantity of at least 1. The Back Office form validates the same rules through its promotion sub-form. Spryker\Zed\DiscountPromotion\Communication\Plugin\Discount
DiscountPromotionDiscountConfiguratorCollectionExpanderPlugin Adds the stored promotional products and the promotion strategy to the discounts the API reads. Spryker\Zed\DiscountPromotion\Communication\Plugin\Discount

src/Pyz/Zed/Discount/DiscountDependencyProvider.php

<?php

namespace Pyz\Zed\Discount;

use Spryker\Zed\Discount\DiscountDependencyProvider as SprykerDiscountDependencyProvider;
use Spryker\Zed\DiscountPromotion\Communication\Plugin\Discount\DiscountPromotionConfigurationOptionsExpanderPlugin;
use Spryker\Zed\DiscountPromotion\Communication\Plugin\Discount\DiscountPromotionDiscountConfiguratorCollectionExpanderPlugin;
use Spryker\Zed\DiscountPromotion\Communication\Plugin\Discount\DiscountPromotionDiscountConfiguratorValidatorRulePlugin;

class DiscountDependencyProvider extends SprykerDiscountDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\DiscountExtension\Dependency\Plugin\DiscountConfigurationOptionsExpanderPluginInterface>
     */
    protected function getDiscountConfigurationOptionsExpanderPlugins(): array
    {
        return [
            new DiscountPromotionConfigurationOptionsExpanderPlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\DiscountExtension\Dependency\Plugin\DiscountConfiguratorValidatorRulePluginInterface>
     */
    protected function getDiscountConfiguratorValidatorRulePlugins(): array
    {
        return [
            new DiscountPromotionDiscountConfiguratorValidatorRulePlugin(),
        ];
    }

    /**
     * @return array<\Spryker\Zed\DiscountExtension\Dependency\Plugin\DiscountConfiguratorCollectionExpanderPluginInterface>
     */
    protected function getDiscountConfiguratorCollectionExpanderPlugins(): array
    {
        return [
            new DiscountPromotionDiscountConfiguratorCollectionExpanderPlugin(),
        ];
    }
}

To add a project-specific business rule, implement Spryker\Zed\DiscountExtension\Dependency\Plugin\DiscountConfiguratorValidatorRulePluginInterface or DiscountVoucherValidatorRulePluginInterface and register the plugin in Pyz\Zed\Discount\DiscountDependencyProvider. The plugins validate the API requests. The Back Office form validates through its form types: to apply the same rule there, add a constraint to the form or to the sub-form your module contributes.

7) Generate the API resources

Generate the API resources, then clear the Glue Backend kernel cache:

docker/sdk cli "GLUE_APPLICATION=GLUE_BACKEND vendor/bin/glue api:generate"
rm -rf data/cache/GlueBackend/*
Verification

Make sure the following classes have been generated:

CLASS PATH
DiscountsBackendResource src/Generated/Api/Backend/DiscountsBackendResource.php
DiscountVoucherCodesBackendResource src/Generated/Api/Backend/DiscountVoucherCodesBackendResource.php
DiscountVoucherCodesGenerateBackendResource src/Generated/Api/Backend/DiscountVoucherCodesGenerateBackendResource.php
DiscountVoucherCodesExportBackendResource src/Generated/Api/Backend/DiscountVoucherCodesExportBackendResource.php
DiscountOptionsBackendResource src/Generated/Api/Backend/DiscountOptionsBackendResource.php

If they do not exist, the schemas were not discovered—check the sourceDirectories setting from step 2.

Verification

Request a Back Office access token as described in Authenticate as a Back Office user, then retrieve the discount options:

curl "https://glue-backend.mysprykershop.com/discount-options" \
  -H "Authorization: Bearer {access_token}" \
  -H "Accept: application/vnd.api+json"

The integration is successful when the request returns 200 with calculatorTypes, collectorFields, and stores populated. If the DiscountPromotion plugins are registered, collectorStrategyTypes lists promotion next to query-string.

Retrieve a discount collection:

curl "https://glue-backend.mysprykershop.com/discounts?page[limit]=1" \
  -H "Authorization: Bearer {access_token}" \
  -H "Accept: application/vnd.api+json"

The request returns 200 with a data array and a meta.pagination object. Every item carries a uuid; an item without one means step 5 has not run.

Create a voucher discount and generate codes for it to confirm the write path:

curl -X POST "https://glue-backend.mysprykershop.com/discounts" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -d '{"data":{"type":"discounts","attributes":{"displayName":"API voucher","discountType":"voucher","isExclusive":false,"validFrom":"2026-01-01 00:00:00","validTo":"2026-12-31 23:59:59","calculatorType":"percentage","percentage":10,"collector":{"rules":[{"field":"sku","operator":"=","value":"*"}]},"decisionRule":{"rules":[]},"minimumItemQuantity":1,"priority":100,"stores":["DE"]}}}'
curl -X POST "https://glue-backend.mysprykershop.com/discounts/{discount_uuid}/voucher-codes/generate" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -d '{"data":{"type":"discount-voucher-codes-generate","attributes":{"quantity":3,"codeLength":6}}}'

Both requests return 201 Created. The first response carries the uuid of the new discount, the second one the voucherBatch, the generatedCount, and the generated codes.

Troubleshooting

SYMPTOM CAUSE
404 with error code 007 while src/Generated/Api/Backend/DiscountsBackendResource.php exists The route is unknown to the API Platform kernel, so the Glue router answered instead. The kernel cache is stale—check that you removed the directory that actually exists under data/cache/GlueBackend/, then re-run step 7 in order. See API Platform troubleshooting.
404 on /discounts and no generated resource class The schema was not discovered. Confirm that spryker/api-platform is installed, and, if your project overrides sourceDirectories(), that it still covers the directory the modules are installed into.
500 on every discount request Either isDiscountUuidEnabled() returns false, or a discount has no uuid yet. The client gets a generic error; the exception in the Glue Backend log says which: complete step 3, or run step 5.
promotion is ignored in requests and missing in responses The getTransferPathByAttribute() mapping of step 3 is not in place.
/discount-options lists query-string only, and a discount with the promotion strategy is rejected The DiscountPromotion plugins are not registered. Complete step 6.
Errors of promotional products are reported with code 5799 instead of 5730 The getResponseCodeByErrorMessage() mapping of step 3 is not in place.