> This page is for Documentation.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://fiftytwo-demo.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://fiftytwo-demo.ferndocs.com/_mcp/server.

# Price a basket from a webshop

If your webshop calculates its own prices, it will eventually disagree with the
till. Someone will bring a basket into a store, get a different number, and you
will spend a day working out which system was right.

[Basket API](/api/basket/overview) exists to remove that class of bug. It runs the same
calculation logic a 52ViKING till runs, so the totals match by construction.

## What it is

One endpoint:

```
POST /basket/calculatebasket/{chain}/{store}
```

Key properties, from the spec:

* **Stateless.** Nothing is persisted: no basket, no receipt. You send the whole
  basket every time and get the whole answer back.
* **XML or JSON**, on both request and response. Pick one with `Content-Type`
  and `Accept`.
* **Horizontally scalable.** Multiple 52ViKING tills can serve the calls behind a
  load balancer.
* **Line-orientated.** Each line you send stays one line in the response. Lines
  are neither collapsed nor expanded, so quantity three is one line, not three.

> **Note**
>
> Basket API is part of the **52MASTERPRICER** suite. It is also genuinely useful
> as a test harness: because it is stateless and side-effect free, you can assert
> on pricing behaviour in CI.

## Chain and store are not optional

Promotions are configured per store. The same article can be on offer in one
store and not in another, so the store number in the path decides the answer.

> **Warning**
>
> Passing a wrong-but-valid store number is the most common cause of "the API
> disagrees with the till". Before debugging further, check that the store number
> you sent is the store you rang the basket up in.

## Making the call

**`cURL`**

```bash title="cURL"
curl -X POST \
  "https://api.fiftytwo.com/basket/basket/calculatebasket/1/0142" \
  -H "Authorization: Bearer $FT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "lines": [
      { "ean": "5701234000019", "quantity": 1 },
      { "ean": "5701234004512", "quantity": 3 }
    ]
  }'
```

**`Python`**

```python title="Python"
import os, requests

BASE = "https://api.fiftytwo.com/basket"

def calculate(chain, store, lines, token):
    resp = requests.post(
        f"{BASE}/basket/calculatebasket/{chain}/{store}",
        headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json",
        },
        json={"lines": lines},
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json()
```

**`TypeScript`**

```typescript title="TypeScript"
const BASE = "https://api.fiftytwo.com/basket";

export async function calculate(
  chain: string,
  store: string,
  lines: Array<{ ean: string; quantity: number }>,
  token: string
) {
  const resp = await fetch(
    `${BASE}/basket/calculatebasket/${chain}/${store}`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({ lines }),
    }
  );
  if (!resp.ok) throw new Error(`Basket ${resp.status}`);
  return resp.json();
}
```

> **Note**
>
> Check the [request schema](/api/basket/reference) for the exact line-item field
> names. The spec carries 35 schemas, and which fields your chain expects depends
> on its configuration.

## Reading the answer

All discounts are applied to articles eligible for discount, and each discount
arrives as its own line referencing the rule that produced it.

That reference is the useful part. It lets you tell a customer *why* the price is
what it is ("3 for 29.00, campaign 881") rather than showing an unexplained
total. Resolve the ID through [Master Data](/api/master-data/overview) if you want the
promotion's display name.

## Where to call it from

#### On the basket page, not just at checkout

The promotion is a reason to add another item. Showing "add one more for 3 for
29.00" is only possible if you priced the basket early.

#### Again at checkout, authoritatively

Campaigns start and end on dates. Re-price immediately before payment so the
customer is charged what they were shown.

#### Cache briefly, if at all

The call is stateless and fast. Caching risks serving a price from before a
campaign flipped, which is usually a worse trade than the round trip.

## Verifying it matches

Ring the same basket on a physical till in the same store and compare. If they
differ:

#### Totals differ by a whole promotion

Almost always the wrong store number, or a campaign that has not been
distributed to that store yet.

#### Totals differ slightly

Check rounding and deposit lines. Returnable-packaging deposits are separate
receipt lines and are not discountable.

#### A discount fires on the till but not the API

Check whether the promotion is a *match discount* (discount basket). Master
Data does not support discount baskets on the Get side, so a rule built that
way through the API can behave differently from one built in Store
Management. See [Buy side and Get side](/help/store-management/discount-groups-buyget).

## Related help-center topics

#### [Discount types](/help/concepts/discount-types)

Every discount shape the engine supports.

#### [Discount groups](/help/store-management/discount-groups)

How the rules are configured.

#### [Buy side / Get side](/help/store-management/discount-groups-buyget)

Trigger articles versus discounted articles.