Skip to main content

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 domains
  • GET /v1/domains/{uuid}/keywords — keywords tracked on a domain
  • GET /v1/domains/{uuid}/competitors — competitors on a domain
  • GET /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

ParameterTypeDefaultMaxDescription
pageinteger1Which page to return (1-based).
per_pageinteger501000How 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:

HeaderDescription
X-Total-CountTotal number of records matching the request.
X-Total-PagesTotal number of pages at the current per_page.
X-Per-PageThe page size actually applied (after the 1000 cap).
X-Current-PageThe 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 id after collecting all pages.
  • An out-of-range page returns an empty data array, not an error — so a loop that checks X-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