Pagination
List endpoints in the Ranktracker API are paginated. Instead of returning every
record at once, they return a single page of results and tell you — via
response headers — how many records and pages exist in total. This guide covers
the page and per_page query parameters and the pagination headers, and shows
how to loop through an entire result set.
Every example uses the production base URL https://api.ranktracker.com, and
all endpoints live under /v1. Send your API key in the Authorization header
with no Bearer prefix — see the
Authentication guide.
Which endpoints paginate
Pagination applies to the collection (GET list) endpoints:
GET /v1/domains— your tracked domainsGET /v1/domains/{uuid}/keywords— keywords tracked on a domainGET /v1/domains/{uuid}/competitors— competitors on a domainGET /v1/tags— your tags
These return an array under data. Single-resource reads (for example
GET /v1/domains/{uuid}) return one object and are not paginated.
Query parameters
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page | integer | 1 | — | Which page to return (1-based). |
per_page | integer | 50 | 1000 | How many records per page. |
per_page is capped at 1000. Requesting more does not return more than the
cap, so always read the response headers (below) rather than assuming you got
everything back in one call.
:::note Larger pages, fewer round-trips
For a large export, prefer a big per_page (up to 1000) over many small
pages — it means fewer requests and less overhead. Combine it with the
filters on an endpoint (for example results_organic_url
on the keywords list) to narrow the set before you page.
:::
Response headers
Every paginated response includes these headers describing the full result set:
| Header | Description |
|---|---|
X-Total-Count | Total number of records matching the request. |
X-Total-Pages | Total number of pages at the current per_page. |
X-Per-Page | The page size actually applied (after the 1000 cap). |
X-Current-Page | The page number this response represents. |
You've reached the end when X-Current-Page equals X-Total-Pages (or when a
page comes back with fewer items than X-Per-Page).
Request a page and read the headers
Ask for 100 records per page and inspect the headers with curl -i (or -D -
to dump headers):
curl -i "https://api.ranktracker.com/v1/domains?per_page=100&page=1" \
-H "Authorization: tkn_usr_your_api_key_here"
A response looks like this — headers first, then the JSON:API body:
HTTP/1.1 200 OK
Content-Type: application/json
X-Total-Count: 237
X-Total-Pages: 3
X-Per-Page: 100
X-Current-Page: 1
{
"data": [
{
"id": "3f2a9c8e-1d4b-4a7f-9e2c-8b1a2c3d4e5f",
"type": "domain",
"attributes": {
"domain": "example.com",
"host": "www.example.com",
"matchType": "any",
"projectName": "Example Site"
}
}
]
}
Here X-Total-Count: 237 and X-Per-Page: 100 mean there are 3 pages
(X-Total-Pages: 3); this response is page 1 of 3.
Loop through every page
Read X-Total-Pages from the first response, then request each subsequent page
until you've fetched them all. Both examples below page through all of your
tracked domains.
import requests
BASE = "https://api.ranktracker.com/v1"
HEADERS = {"Authorization": "tkn_usr_your_api_key_here"}
PER_PAGE = 1000
def fetch_all_domains():
domains = []
page = 1
while True:
resp = requests.get(
f"{BASE}/domains",
headers=HEADERS,
params={"page": page, "per_page": PER_PAGE},
)
resp.raise_for_status()
domains.extend(resp.json()["data"])
total_pages = int(resp.headers["X-Total-Pages"])
if page >= total_pages:
break
page += 1
return domains
all_domains = fetch_all_domains()
print(f"Fetched {len(all_domains)} domains")
const BASE = "https://api.ranktracker.com/v1";
const HEADERS = { Authorization: "tkn_usr_your_api_key_here" };
const PER_PAGE = 1000;
async function fetchAllDomains() {
const domains = [];
let page = 1;
while (true) {
const url = `${BASE}/domains?per_page=${PER_PAGE}&page=${page}`;
const resp = await fetch(url, { headers: HEADERS });
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const body = await resp.json();
domains.push(...body.data);
const totalPages = Number(resp.headers.get("X-Total-Pages"));
if (page >= totalPages) break;
page += 1;
}
return domains;
}
const allDomains = await fetchAllDomains();
console.log(`Fetched ${allDomains.length} domains`);
The same loop works for any list endpoint — swap the path for
/v1/domains/{uuid}/keywords, /v1/domains/{uuid}/competitors, or /v1/tags.
:::tip Be kind when looping
When paging through large collections, request a full per_page=1000 to keep
the page count low, and handle a 503 by backing off and retrying — throttled
requests currently return 503 (there are no RateLimit-* headers yet). See
Errors & rate limits for the retry
strategy.
:::
Notes and edge cases
- Stable ordering isn't guaranteed across writes. If records are created or
deleted while you page, a record can shift between pages. For a consistent
snapshot, page through quickly, or de-duplicate by
idafter collecting all pages. - An out-of-range
pagereturns an emptydataarray, not an error — so a loop that checksX-Total-Pages(as above) will terminate cleanly. - Quota still applies. Pagination controls how much data comes back per request; it does not change what counts against your plan. Check your limits with Usage & quota.
Next steps
- Core concepts — the JSON:API envelope and resource model behind every list response.
- Errors & rate limits — status codes and
the
503retry-with-backoff strategy for long-running loops. - Bulk operations — tag many keywords at once instead of one request per keyword.
- API reference — every list endpoint and its filters: Domains, Keywords, Competitors, and Tags.