Coupons and promotion codes

Discount codes in Áskell are made up of two objects that work together:

  • A coupon (coupon) defines the discount itself: a fixed amount (amount_off and currency) or a percentage (percent_off), how long it applies (duration: forever, once or repeating with duration_in_months) and limits such as max_redemptions and redeem_by.

  • A promotion code (promotion_code) is the code the customer enters (code), and it points to a coupon. You can create many codes for the same coupon, restrict a code to a single customer, and set an expiry date (expires_at) and a minimum amount in restrictions.

Both objects can be created, fetched, updated and deleted through the V2 API. All calls require a secret API key. Legacy token authentication is not supported on these endpoints:

Authorization: Api-Key your-secret-api-key

Full documentation of fields and responses is in our Swagger documentation under V2 Coupons at /api/swagger/v2/.

Coupons

Endpoint

Description

GET /api/v2/coupons/

Lists coupons. Filters: valid, duration, currency, name. page_size turns on pagination.

POST /api/v2/coupons/

Creates a coupon.

GET /api/v2/coupons/{couponId}/

Retrieves a single coupon.

PATCH /api/v2/coupons/{couponId}/

Partially updates a coupon.

DELETE /api/v2/coupons/{couponId}/

Deletes a coupon (soft delete).

Example of creating a 20% coupon that applies for three months:

{
  "name": "Sumartilboð",
  "percent_off": "20",
  "duration": "repeating",
  "duration_in_months": 3,
  "max_redemptions": 100,
  "redeem_by": "2026-12-31T23:59:59Z"
}

Rules:

  • Exactly one of amount_off (together with currency) or percent_off must be set. To switch from one to the other with PATCH, send the old field as null in the same call.

  • duration_in_months is required when duration is repeating and must not be set otherwise.

  • redeem_by must be in the future unless the coupon has already expired; an expired coupon can still be updated and deleted, but a valid coupon cannot be made to expire by setting a date in the past.

  • Unknown fields in POST and PATCH are rejected with 400. metadata and restrictions can be sent as null to clear them.

  • id and times_redeemed cannot be changed. id can be supplied on creation (letters, digits, underscores, hyphens and periods, at most 128 characters); otherwise it is generated automatically.

  • currency must be a valid three-letter ISO 4217 code. max_redemptions and duration_in_months must be 1 or higher.

  • A coupon that has been redeemed (times_redeemed > 0) cannot be deleted. Use redeem_by or max_redemptions to close it.

Limiting a coupon to plans and products

A coupon can be limited to specific legacy plans (applies_to_plans, a list of plan ids), to specific variants of them (applies_to_plan_variants, a list of plan variant ids), to specific products (applies_to_products, a list of product ids) and to specific prices of them (applies_to_prices, a list of price ids: the same id that GET /api/v2/catalog/prices/ returns and an offer line’s price). All four lists are empty by default, and the coupon then applies to everything:

{
  "name": "Afsláttur af grunnpakka",
  "percent_off": "10",
  "duration": "forever",
  "applies_to_products": [42]
}
  • As soon as any of the lists has an entry the coupon is restricted: a legacy subscription only gets the discount if its plan is in applies_to_plans or its plan variant in applies_to_plan_variants, and a contract line only if its product is in applies_to_products or its price in applies_to_prices. A coupon limited only to plans or plan variants therefore gives no discount on contracts, and the other way round.

  • A plan in applies_to_plans covers all of its variants, while a plan variant in applies_to_plan_variants covers only itself. That way a coupon can, for example, discount a monthly subscription without giving the same discount on a prepaid year of the same plan.

  • In the same way, a product in applies_to_products covers all of its prices, while a price in applies_to_prices only covers itself. A coupon can, for example, discount a product’s monthly price but not the yearly price of the same product:

    {
      "name": "Fyrsti mánuðurinn á hálfvirði",
      "percent_off": "50",
      "duration": "once",
      "applies_to_prices": [101]
    }
    
  • On a contract the discount is calculated from the lines it covers alone: percent_off is a percentage of those lines and amount_off is at most their total.

  • A contract line is judged by the price it is on at the time. If an item moves from a monthly price the coupon is limited to onto a yearly price it is not limited to, it gets no discount, and gets it again if it moves back.

  • A code is refused when nothing in the purchase, or on the contract it is applied to, is covered by the coupon.

  • If a subscription moves to a plan or plan variant outside the lists, the discount stays on it but takes nothing off until it moves back to a plan or plan variant the coupon covers. A once coupon is not used up by a payment it did not reduce.

  • Plans, plan variants, products and prices must belong to the same account. In a PATCH, a list that is left out stays unchanged and an empty list ([]) clears it.

  • A plan, plan variant, product or price that a coupon is limited to cannot be deleted, nor can a plan with a variant, or a product with a price, that a coupon is limited to.

Promotion codes

Endpoint

Description

GET /api/v2/promotion-codes/

Lists promotion codes. Filters: code, coupon, active, valid, customer_reference.

POST /api/v2/promotion-codes/

Creates a promotion code.

GET /api/v2/promotion-codes/{promotionCodeId}/

Retrieves a single promotion code.

PATCH /api/v2/promotion-codes/{promotionCodeId}/

Partially updates a promotion code.

DELETE /api/v2/promotion-codes/{promotionCodeId}/

Deletes a promotion code (soft delete).

Example of creating a code that a single customer can redeem only once:

{
  "coupon": "coup_5f3a1c2b9d8e4f6a7b8c9d0e",
  "code": "VELKOMIN",
  "customer_reference": "customer-123",
  "max_redemptions": 1,
  "expires_at": "2026-12-31T23:59:59Z",
  "restrictions": {
    "minimum_amount": "1000",
    "minimum_amount_currency": "ISK"
  }
}

Rules:

  • coupon must be a coupon on the same account.

  • code is 4-50 characters (letters, digits, hyphens and underscores) and is always stored in uppercase. If it is omitted, a code is generated automatically. The code must be unique among the account’s active codes; inactive (active: false) and deleted codes free the code up for reuse.

  • The customer is given with customer (ID) or customer_reference, not both. null removes the customer binding.

  • restrictions supports only a minimum amount: minimum_amount (greater than 0) and minimum_amount_currency (ISO 4217 code) must be sent together. An empty {} removes the restriction.

  • A promotion code that has been redeemed cannot be deleted. Set active to false instead.

Applying codes to subscriptions is documented with the subscription endpoints: see POST /api/v2/subscription-contracts/{contractId}/apply-code/ for V2 contracts and POST /api/subscriptions/{subscription_id}/apply-code/ for legacy subscriptions.