Keyword performance changes
curl --request GET \
--url https://api.snowseo.com/v3/rank-tracking/keyword-changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.snowseo.com/v3/rank-tracking/keyword-changes"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.snowseo.com/v3/rank-tracking/keyword-changes', 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.snowseo.com/v3/rank-tracking/keyword-changes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$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.snowseo.com/v3/rank-tracking/keyword-changes"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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.snowseo.com/v3/rank-tracking/keyword-changes")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.snowseo.com/v3/rank-tracking/keyword-changes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"page": 123,
"pageSize": 123,
"total": 123,
"totalPages": 123,
"items": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"rising": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"falling": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"new": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"lost": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"categories": [
{
"key": "<string>",
"label": "<string>",
"count": 123
}
]
}{
"success": true,
"error": "<string>",
"code": "<string>",
"message": "<string>",
"reference": "<string>"
}{
"success": true,
"error": "<string>",
"code": "<string>",
"message": "<string>",
"reference": "<string>"
}Rank Tracking
Keyword Changes
See which tracked keywords moved up or down in search rankings.
GET
/
v3
/
rank-tracking
/
keyword-changes
Keyword performance changes
curl --request GET \
--url https://api.snowseo.com/v3/rank-tracking/keyword-changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.snowseo.com/v3/rank-tracking/keyword-changes"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.snowseo.com/v3/rank-tracking/keyword-changes', 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.snowseo.com/v3/rank-tracking/keyword-changes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$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.snowseo.com/v3/rank-tracking/keyword-changes"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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.snowseo.com/v3/rank-tracking/keyword-changes")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.snowseo.com/v3/rank-tracking/keyword-changes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"page": 123,
"pageSize": 123,
"total": 123,
"totalPages": 123,
"items": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"rising": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"falling": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"new": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"lost": [
{
"keyword": "<string>",
"clicks": 123,
"impressions": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"positionChange": 123,
"previousPosition": 123,
"previousClicks": 123,
"previousImpressions": 123,
"category": "rising",
"pages": 123,
"rankedPages": [
{
"page": "<string>",
"clicks": 123,
"impressions": 123,
"ctr": 123,
"position": 123,
"clicksChange": 123,
"impressionsChange": 123,
"ctrChange": 123,
"positionChange": 123
}
]
}
],
"categories": [
{
"key": "<string>",
"label": "<string>",
"count": 123
}
]
}{
"success": true,
"error": "<string>",
"code": "<string>",
"message": "<string>",
"reference": "<string>"
}{
"success": true,
"error": "<string>",
"code": "<string>",
"message": "<string>",
"reference": "<string>"
}Find out which keywords improved or dropped in position between a period and the one before it. Great for daily or weekly monitoring to catch ranking shifts early.
For tracked keywords with no Search Console data,
endDate is required; the request returns 400 without it. Send endDate without startDate for All Time: the four lists come back empty, because All Time has no previous period to compare against. The default scope=all includes Search Console queries alongside your tracked keywords.
Limit and paging
- Default mode: when
pageis not sent andpageSizeis notall,limit(default50) caps the Search Console queries fetched for each period and trims each of the four lists (rising,falling,new,lost) to that many.categoriescounts reflect these trimmed lists. pageSize=allwithoutpage: Search Console queries are fetched uncapped and the four lists are returned in full.- With
page: Search Console queries are fetched uncapped and the response is{ items, page, pageSize, total, totalPages, categories }, with norisingorfallinglists.itemsmixes all four categories unless you passcategory.categoriescounts come from the full lists (respectingsearch).
Key fields
| Field | What It Tells You |
|---|---|
rising | Keywords that moved closer to position #1 |
falling | Keywords that dropped in position |
new | Keywords that just started ranking |
lost | Keywords that fell off the rankings entirely |
categories | [{ key, label, count }] for Rising, Falling, New, and Lost |
Keyword fields
Each keyword in a list (or initems) has:
| Field | What It Tells You |
|---|---|
keyword | The keyword or query |
category | rising, falling, new, or lost |
position | Average position in this period (0 for lost keywords) |
previousPosition | Average position in the previous period (0 for new keywords) |
positionChange | Positions gained (positive) or lost (negative). 0 for new keywords |
clicks / previousClicks | Clicks in this and the previous period |
clicksChange | Percent change in clicks |
impressions / previousImpressions | Impressions in this and the previous period |
impressionsChange | Percent change in impressions |
pages | Number of pages ranking for the keyword |
rankedPages | The ranking pages, each with its own metrics and changes |
clicks and impressions are null, and the change and previous click and impression fields are 0.
A positive
positionChange means the keyword moved closer to position #1 (good). A negative positionChange means it dropped. For lost keywords it is the negative of the previous position.Authorizations
bearerAuthapiKey
Your SnowSEO API key (sk_...).
Query Parameters
Required range:
x >= 1Available options:
kw, pos, imp, clicks, pages, posDelta Available options:
asc, desc Available options:
rising, falling, new, lost Team ID
Start date (YYYY-MM-DD, UTC). Omit with endDate set for All Time: the lists come back empty, as All Time has no previous period.
End date (YYYY-MM-DD, UTC)
Max keywords returned per category (default 50). Category counts always cover every keyword.
Rows per category, or all.
'true' adds each keyword's ranking pages (pages, rankedPages). Slower; defaults to 'false'.
Available options:
true, false Device filter
Search type filter
Country filter
Keyword universe: all (GSC + tracked), ranked (GSC only), tracked (tracked only). Defaults to all.
Available options:
all, ranked, tracked Was this page helpful?

