CnterDocumentationSign in

Start here

  • What Cnter is
  • Core concepts
  • Quickstart

Platform

  • Authentication
  • Conventions
  • Errors
  • Organization
  • The dashboard

Modules

  • Content Management
  • Online Ordering
  • Restaurant Ordering
  • Promotions
  • Reservations
  • Bookings
  • Contact Form

Payments

  • AgnusPAY
  • Tax

Services

  • Address autocomplete
  • Video
  • Email

Reference

  • API reference

Modules

Promotions

Automatic discounts and codes. Applied server-side at checkout, previewable in the cart.

A full brief for whoever builds against this.

Promotions are applied by the server, at checkout, whether or not your site knows they exist. There is one endpoint here, and it is not for applying discounts. It is for showing the buyer, before they pay, what the server is going to do.

Automatic and coded#

An automatic promotion needs nothing from the buyer. Ten percent off, happy hour, buy one get one: it holds because the cart matches the conditions your customer set.

A coded promotion needs a code, typed into a field you provide and passed as discount_code at checkout. Everything else about it is the same.

Your customer configures both in the dashboard: the effect, who it applies to, when it runs and how often it can be used.

ConfiguredOptions
Effectpercentage off, a fixed amount off, or a bundle deal at item level.
ScopeThe whole order, specific catalog items, or whole categories.
ConditionsA minimum order amount, a first-order-only flag, a start and end date, a recurring window such as weekday afternoons.
LimitsA maximum number of redemptions across all buyers.
OrderA priority, which decides how several promotions stack.

Previewing the cart#

POST/api/public/{organizationId}/promotions/previewPublic

Given a cart and an optional code, returns which promotions apply, in what order, and the resulting price.

Request
{
  "items": [
    { "content_item_id": "8f2a1c34-...", "quantity": 2 }
  ],
  "code": "WELCOME10"
}

Send nothing else. Items are required, up to 100 lines with quantities up to 1000. The code is optional: omit it entirely if the buyer typed nothing.

Response
{
  "subtotal": 55.00,
  "lines": [
    { "promotion_id": "c81a...", "name": "Happy hour",
      "effect_type": "percentage", "value": 10, "discount_amount": 5.50 }
  ],
  "code": { "valid": true },
  "total_discount": 5.50,
  "final": 49.50
}
FieldRender as
linesOne discount row each, name on the left and the amount as a deduction. The order is the order they stack in.
code{ valid: true } means it applied and is already counted in lines. { valid: false } means show "code not valid". null means no code was entered.
total_discountThe total discount.
finalWhat the buyer pays for the items.

It works on the items subtotal only, so fees, the delivery fee and tax are not in it. That makes it the wrong call to hang a cart on: the quote on Online Ordering runs the same discount engine and returns discount.total, discount.lines and the same verdict on the code, alongside everything else the customer will be charged. Ask the quote whenever the cart changes, and keep this one for what it is good at — checking a code the moment it is typed, before there is a basket worth quoting.

Why an invalid code says nothing more

The response never explains why a code failed. Expired, used up, wrong cart and never existed all look the same, because the difference is a way to probe someone else's promotions from the outside.

One deliberate blind spot#

The preview takes no email address, so first-order-only promotions cannot be evaluated and are skipped. The checkout does have the email and applies them there, so a genuine first-time buyer sees the discount on the payment page and in the final total.

The preview can therefore understate a discount, and never overstate one. That direction is chosen: an endpoint that answered differently for an address that had ordered before would be a way to test whether a given person is a customer of this business, from an unauthenticated request.

At checkout#

You do not apply anything. Pass the code the buyer typed as discount_code in the checkout body and let the server work out the rest. Automatic promotions need no mention at all.

json
{
  "items": [ ],
  "customer": { },
  "discount_code": "WELCOME10"
}

Never compute the discount yourself

The preview is for display. The final amounts always come from the server, which applies discounts to the taxable base, taxes fees at their own rate and rounds once per rate group. Two systems doing that arithmetic will eventually disagree in front of a buyer.

When it fails#

On an error or an empty response, show no discount and the plain subtotal. A promotion that fails to display costs a little goodwill. A cart that refuses to render costs the order.

The endpoint is rate limited per organization and IP, at 30 requests a minute. Debounce it to cart changes rather than firing on every keystroke in the code field.

PreviousRestaurant OrderingNextReservations

On this page

  • Automatic and coded
  • Previewing the cart
  • One deliberate blind spot
  • At checkout
  • When it fails