docs / endpoint
POST /v1/serp/history
Position history for a (domain, keyword) pair from our SERP archive.
Credits: 5 credits per call.
Timeout: 60s
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
domain |
string | required | — | Domain to check ranking history for (bare hostname, lowercased). Required, max 255 chars, no spaces. |
keyword |
string | required | — | Keyword to check ranking history for. Required, max 700 chars. |
engine |
string | optional | "google" |
Optional, default "google". Same enum as /v1/search. |
location |
any | optional | 2840 |
Google Ads geotarget ID. Optional, default 2840 (United States). Look one up with /v1/locations. |
language |
string | optional | "en" |
ISO language code. Optional, default "en". |
device |
string | optional | "desktop" |
One of: desktop, mobile. Optional, default "desktop". |
date_from |
string | optional | — | ISO YYYY-MM-DD, inclusive. Optional, default 730 days ago -- the rankings archive's TTL floor. An empty result is still a chargeable answer: coverage.observations counts the distinct days we archived this exact SERP in the window, so it can mean either "we observed you on N days and you never ranked" (observations > 0) or "we never looked" (observations: 0). |
date_to |
string | optional | — | ISO YYYY-MM-DD, inclusive. Optional, default today. |
Request
→ requestPOST /v1/serp/history
POST /v1/serp/history HTTP/1.1
Authorization: Bearer sof_live_your_key_here
Content-Type: application/json
{
"domain": "example.com",
"keyword": "best running shoes",
"engine": "google",
"location": 2840,
"language": "en",
"device": "desktop",
"date_from": "2026-05-20",
"date_to": "2026-07-28"
}
Response
Wrapped in the standard envelope -- id, object, created_at, elapsed_ms, cache, credits -- documented once on The Contract. The body below is a real envelope with this endpoint's data shape; values vary per request, and a … marks an array cut short for display.
← response200
{
"id": "serp_d5ol4mx5bnnin24qqnksxcz5",
"request_id": "req_dhxllbqudbfwhaay4p2eo4ajza",
"object": "serp_history",
"created_at": "2026-08-09T07:23:32Z",
"elapsed_ms": 180,
"cache": "miss",
"credits": {
"charged": 5,
"balance": 9857
},
"data": {
"domain": "example.com",
"keyword": "best running shoes",
"engine": "google",
"location": 2840,
"language": "en",
"device": "desktop",
"points": [
{
"date": "2026-06-03",
"rank": 9,
"url": "https://example.com/best-running-shoes",
"type": "organic"
},
{
"date": "2026-06-17",
"rank": 6,
"url": "https://example.com/best-running-shoes",
"type": "organic"
}
],
"first_seen": "2026-06-03",
"best_rank": 6,
"coverage": {
"from": "2026-05-20",
"observations": 14
}
}
}
Response fields
What each field in data (above) means.
| Field | Description |
|---|---|
data.domain |
The domain you requested. |
data.keyword |
The keyword you requested. |
data.engine |
Search engine this history is drawn from. |
data.location |
Google Ads geotarget ID this history is drawn from. |
data.language |
ISO language code this history is drawn from. |
data.device |
Device this history is drawn from: desktop or mobile. |
data.points |
One entry per day with an archived observation for this (domain, keyword) pair, in the requested date window. |
data.points[].date |
Date of this observation, YYYY-MM-DD. |
data.points[].rank |
Absolute rank on that day's results page, or null if the domain didn't appear. |
data.points[].url |
URL that ranked, or null if the domain didn't appear. |
data.points[].type |
Result type this rank came from, e.g. "organic". |
data.first_seen |
Date of the earliest point in this response, or null if points is empty. |
data.best_rank |
Best (lowest-numbered) rank across all points, or null if the domain never appeared. |
data.coverage |
How much of the rankings archive this response drew from. |
data.coverage.from |
Earliest date the archive could have observed, floor-clamped to the archive's retention window. |
data.coverage.observations |
Number of distinct days we archived this exact result page in the window -- can be > 0 even when every point shows no ranking. |