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:
| Field | Meaning |
|---|---|
tenant | Your tenant's name, for example acme |
exp | When the token expires, in Unix seconds. Keep it short, for example 15 minutes, and mint a new one as the session goes on |
filter | The 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();
| Parameter | Required | Meaning |
|---|---|---|
skus | Yes | The SKUs to price, comma-separated |
tax_area | Yes | The tax area to compute VAT for |
currency_code | When the token's filter has none | The currency to price in |
token | When not sent as Authorization: Bearer | The 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
| Status | Cause |
|---|---|
400 | skus or tax_area is missing, or no currency is given |
401 | The token is missing, malformed, wrongly signed or expired, was minted for another tenant, or its filter selects no pricelist |
503 | The tenant has no storefront key yet: call GET /storefront/key once first |