Skip to main content

Storefront Prices

A shop page can fetch its prices straight from the shopper's browser, without your API key ever reaching the browser. Your server gives the page a short-lived storefront token that says which pricelists the shopper may see; the browser calls Quantaprice with it.

Use it when prices should load with the page from the browser, for example on a B2B storefront where each signed-in customer has their own pricelist. A server that renders the page can call POST /price/query instead.


1. Get the Signing Key​

Your server fetches the tenant's storefront signing key once, with an ordinary access token, and stores it with its other secrets:

curl -s "https://YOUR_TENANT.quantaprice.com/api/storefront/key" \
-H "Authorization: Bearer $QP_TOKEN"
{ "key": "3f7b9c2e4a1d6f8b0c5e7a9d2b4f6e8a1c3e5b7d9f0a2c4e6b8d0f1a3c5e7b9" }

The key is created on the first call and stays the same after it. It must never reach a browser: anyone with it can mint tokens for any pricelist.


2. Mint a Token per Shopper​

When a shopper signs in, or starts a session, your server mints a token for the pricelists they may see. The token is

base64url(payload) + "." + hex(HMAC-SHA256(base64url(payload), key))

where base64url has no padding, the HMAC key is the key string as it is, and payload is JSON:

FieldMeaning
tenantYour tenant's name, for example acme
expWhen the token expires, in Unix seconds. Keep it short, for example 15 minutes, and mint a new one as the session goes on
filterThe pricelists the shopper may see, as a pricelist filter: pricelists, currency_code, metadata

In Node.js:

import crypto from 'node:crypto';

function storefrontToken(key, tenant, filter, ttlSeconds = 900) {
const payload = Buffer.from(JSON.stringify({
tenant,
exp: Math.floor(Date.now() / 1000) + ttlSeconds,
filter,
})).toString('base64url');
const signature = crypto.createHmac('sha256', key).update(payload).digest('hex');
return `${payload}.${signature}`;
}

const token = storefrontToken(process.env.QP_STOREFRONT_KEY, 'acme',
{ pricelists: ['customer-acme'], currency_code: 'SEK' });

In Python:

import base64, hashlib, hmac, json, time

def storefront_token(key: str, tenant: str, filter: dict, ttl_seconds: int = 900) -> str:
payload = json.dumps({"tenant": tenant, "exp": int(time.time()) + ttl_seconds, "filter": filter})
p = base64.urlsafe_b64encode(payload.encode()).rstrip(b"=").decode()
return p + "." + hmac.new(key.encode(), p.encode(), hashlib.sha256).hexdigest()

3. Fetch Prices in the Browser​

const response = await fetch(
'https://YOUR_TENANT.quantaprice.com/api/storefront/prices?skus=SKU-CHAIR-001,SKU-DESK-002&tax_area=SE',
{ headers: { Authorization: `Bearer ${token}` } });
const prices = await response.json();
ParameterRequiredMeaning
skusYesThe SKUs to price, comma-separated
tax_areaYesThe tax area to compute VAT for
currency_codeWhen the token's filter has noneThe currency to price in
tokenWhen not sent as Authorization: BearerThe storefront token

The answer lists the best price of each SKU that has one on the token's pricelists, priced now, as POST /price/query selects it:

[
{
"sku": "SKU-CHAIR-001",
"is_bundle": false,
"currency_code": "SEK",
"sales_price_ex_vat": 2390.0,
"sales_price_inc_vat": 2987.5,
"vat_amount": 597.5,
"vat_rate": 25.0,
"quantity_breaks": [
{ "quantity_break": 10.0, "sales_price_ex_vat": 2150.0, "sales_price_inc_vat": 2687.5, "vat_amount": 537.5 }
]
}
]

Show sales_price_inc_vat or sales_price_ex_vat, as your shop does. A quantity break applies from its quantity_break up; below the lowest break, the base price applies. The answer carries only what a shop page shows: never the price fields you store beside the price, such as a cost price, who wrote the price, or which pricelist it came from. The call works from any web origin, and the browser cannot widen the token's selection of pricelists.

Responses are marked Cache-Control: private, no-store: fetch prices with each page view, as the price can change at any second.

Storefront requests do not count against your query rate limit.


Errors​

StatusCause
400skus or tax_area is missing, or no currency is given
401The token is missing, malformed, wrongly signed or expired, was minted for another tenant, or its filter selects no pricelist
503The tenant has no storefront key yet: call GET /storefront/key once first