curl --request GET \
--url https://api.syntalic.com/v1/analyst/price-change-leadersimport requests
url = "https://api.syntalic.com/v1/analyst/price-change-leaders"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.syntalic.com/v1/analyst/price-change-leaders', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.syntalic.com/v1/analyst/price-change-leaders",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.syntalic.com/v1/analyst/price-change-leaders"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.syntalic.com/v1/analyst/price-change-leaders")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.syntalic.com/v1/analyst/price-change-leaders")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"category": "<string>",
"brand": "<string>",
"country": "<string>",
"currency": "<string>",
"win": 7,
"rising": 123,
"stable": 123,
"falling": 123,
"movers": [
{
"product_uid": "<string>",
"title": "<string>",
"brand": "<string>",
"change_pct": -12.5,
"price_then": 123,
"price_now": 123
}
],
"coverage": {
"products": 42,
"sufficient": true,
"note": "<string>",
"unpublished_note": "<string>",
"sku_note": "<string>",
"rank_note": "<string>"
},
"freshness": {
"observedThrough": "2023-11-07T05:31:56Z",
"staleHours": 6.5
},
"quality": {},
"resolved": {
"input": "<string>",
"category_path": "<string>",
"department": "<string>",
"match_source": "<string>",
"match_confidence": 123,
"alternates": [
{}
]
},
"meta": {
"served_from": "<string>",
"freshness_seconds": 123,
"schema_version": "<string>",
"computed_at": "2023-11-07T05:31:56Z",
"price_observed_at_min": "2023-11-07T05:31:56Z",
"price_observed_at_max": "2023-11-07T05:31:56Z"
}
}{
"type": "<string>",
"title": "Bad Request",
"status": 400,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Payment Required",
"status": 402,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Not Found",
"status": 404,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Too Many Requests",
"status": 429,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Internal Server Error",
"status": 500,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}Rank the biggest price movers in a category or brand
Per-product price-change leaderboard: the biggest droppers and gainers in a category or for a brand. Each product’s change is LIKE-FOR-LIKE between two matched bands - the best price now versus the best price a window ago - over win = 7, 30 (default) or 90 days, which is the same comparison the Syntalic dashboards show. Only the two bands are scanned, never the dead middle of the window, so a product observed twice in one day cannot masquerade as a 90-day move. Changes beyond 50% in either direction are dropped as data errors rather than reported as news: a product does not fall 97% in a month, a mis-keyed price does, and it would otherwise top a list sorted by absolute change. The rising/stable/falling counts describe the WHOLE matched set the movers were drawn from, so a leaderboard can never imply more churn than the shelf actually had. DISTINCT from /v1/analyst/inflation, which is the category-level read over a quarter or longer; this is which individual SKUs moved. At least one of category or brand is required.
curl --request GET \
--url https://api.syntalic.com/v1/analyst/price-change-leadersimport requests
url = "https://api.syntalic.com/v1/analyst/price-change-leaders"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.syntalic.com/v1/analyst/price-change-leaders', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.syntalic.com/v1/analyst/price-change-leaders",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.syntalic.com/v1/analyst/price-change-leaders"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.syntalic.com/v1/analyst/price-change-leaders")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.syntalic.com/v1/analyst/price-change-leaders")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"category": "<string>",
"brand": "<string>",
"country": "<string>",
"currency": "<string>",
"win": 7,
"rising": 123,
"stable": 123,
"falling": 123,
"movers": [
{
"product_uid": "<string>",
"title": "<string>",
"brand": "<string>",
"change_pct": -12.5,
"price_then": 123,
"price_now": 123
}
],
"coverage": {
"products": 42,
"sufficient": true,
"note": "<string>",
"unpublished_note": "<string>",
"sku_note": "<string>",
"rank_note": "<string>"
},
"freshness": {
"observedThrough": "2023-11-07T05:31:56Z",
"staleHours": 6.5
},
"quality": {},
"resolved": {
"input": "<string>",
"category_path": "<string>",
"department": "<string>",
"match_source": "<string>",
"match_confidence": 123,
"alternates": [
{}
]
},
"meta": {
"served_from": "<string>",
"freshness_seconds": 123,
"schema_version": "<string>",
"computed_at": "2023-11-07T05:31:56Z",
"price_observed_at_min": "2023-11-07T05:31:56Z",
"price_observed_at_max": "2023-11-07T05:31:56Z"
}
}{
"type": "<string>",
"title": "Bad Request",
"status": 400,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Payment Required",
"status": 402,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Not Found",
"status": 404,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Too Many Requests",
"status": 429,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}{
"type": "<string>",
"title": "Internal Server Error",
"status": 500,
"detail": "<string>",
"instance": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
}
}Query Parameters
Product category. Accepts a human-readable name (e.g. 'electronics', 'beauty') or a taxonomy slug path (e.g. 'electronics/headphones'); matched against the catalog's category tree (exact node, subtree, or path substring). Also accepts a GS1 GPC code (exactly 8 digits, e.g. '10001159') to filter by PRODUCT TYPE instead of by shelf placement — the same product type is shelved under different categories by different retailers, so a category filter answers from a fraction of the data while a GPC code spans them. Coarser codes (class/family/segment) match every brick beneath them. Resolve a phrase to a code with /v1/reference/classify?q=... ; when a code is used, resolved.match_source is 'gpc'.
1Brand name (e.g., Sony, Samsung, Nike)
1Country (us or ca)
us, ca Comparison window in days. Only the canon bands are accepted.
7, 30, 90 Max results to return (default 15, min 1, max 100)
1 <= x <= 100Response
Price change leaders
7, 30, 90 Show child attributes
Show child attributes
How much data backed this answer. sufficient is false when the slice is too thin to read as a market statement; the count is always reported so you can judge for yourself, and note explains an empty or thin result rather than leaving you to infer it.
Show child attributes
Show child attributes
How current the underlying observations are. observedThrough is the newest observation behind this answer; staleHours is its age. Both null when nothing was observed.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes

