API reference
Send the changes. Ask for the number.
Every request is authenticated with a bearer API key. Prices are decimal strings, dates are ISO 8601, and the anchor endpoint is free because it belongs on a product page.
Authentication
Create keys in the dashboard. A key is shown once and stored only as a SHA-256 digest, so a lost key is replaced rather than recovered.
Authorization: Bearer pp_live_XXXXXXXXXXXXXXXXXXXXXXXXEndpoints
- POST
/api/v1/pricesRecord one or more price changes.
Accepts a single object or a batch of up to 1,000. An unchanged price writes nothing, so it is safe to send on every product save.
- GET
/api/v1/anchor/{externalId}The reference price and a ready-made badge.
Free and unmetered — it is on your product page. Add ?locale=de|en|fr for the badge text.
- POST
/api/webhooks/shopify/{slug}Shopify product webhook.
Verified over the raw body with your shop's signing secret. Redelivery is idempotent.
# Record a price change
curl -X POST https://pricepulse.altixcode.com/api/v1/prices \
-H "Authorization: Bearer pp_live_..." \
-H "Content-Type: application/json" \
-d '{"externalId":"variant_9001","price":"70.00","title":"Winter coat"}'
# Ask for the badge
curl https://pricepulse.altixcode.com/api/v1/anchor/variant_9001?locale=de \
-H "Authorization: Bearer pp_live_..."The rules, precisely
| Rule | How it is implemented |
|---|---|
| The window | The 30 days before the reduction took effect — not the 30 days before now. The current price is excluded from its own reference, or nothing would ever be a reduction. |
| The far edge | Inclusive. A price still in force when the window opened counts, even if it was set months earlier. This is the case a naive 'filter the last 30 days of rows' query gets wrong. |
| The percentage | Divided by the reference price, per C-330/23 — never by the previous price. Rounded down. |
| New products | Art. 6a(2): a shorter reference period for goods offered for less than 30 days. The badge says so rather than implying a full month of history. |
| Staged sales | Art. 6a(3): a progressively increasing reduction may keep the price from before the sequence began. Off by default — it is an exemption, and claiming it wrongly is worse than not claiming it. |
Why your discount got smaller
This is the first thing every customer asks, so the answer is in the response. Alongside the lawful figure the anchor endpoint returns naivePercentFromPrevious — what a previous-price comparison would have advertised.
{
"anchorPrice": "80.00", // the 30-day low
"reductionPercent": "12", // lawful: (80 - 70) / 80
"naivePercentFromPrevious": "30" // what you would have shown: (100 - 70) / 100
}It is returned so you can see the gap and decide what to do about it — run the sale deeper, or advertise the honest number. It is never for display: showing it is the practice the directive prohibits.
The badge text
Generated rather than left to your theme, because the wording is part of the obligation. A bare crossed-out number does not tell a consumer what it is, and “statt 100 €” implies the price was 100 until recently — which is the impression the rule exists to prevent.
"badge": {
"show": true,
"price": "70,00",
"anchorPrice": "80,00",
"anchorLabel": "Niedrigster Preis der letzten 30 Tage",
"percentLabel": "12 % reduziert",
"savingsLabel": "Sie sparen 10,00 EUR",
"locale": "de"
}Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_payload | The body failed validation. `details.issues` names each field. |
| 400 | invalid_price | A price was zero, negative or not a decimal. |
| 400 | future_price | A price cannot take effect in the future. |
| 401 | missing_api_key / invalid_api_key | No bearer token, or one we do not recognise. |
| 404 | not_found | No variant with that external id in your organisation. |
| 409 | no_history | The variant has no recorded price, so no reduction can be announced. |