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, an ISO country code (e.g. "US"), or a country name. 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, which is as far back as the archive goes. 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
The usual envelope (id, request_id, object, created_at, elapsed_ms, credits) is documented once on The Contract. Below is an example response with this endpoint's data. Values vary per request; … marks an array cut short.
← response200
{
"id": "serp_dz66m7eqjbpfblkclv6jye2g",
"request_id": "req_id5g52qgwzmmnoafbmteiscgvi",
"object": "serp_history",
"created_at": "2026-07-29T12:00:00Z",
"elapsed_ms": 244,
"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 |
Rank among results of the same type on that day's page -- organic rank for organic points -- 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) organic rank across all points, or null if the domain never ranked organically. |
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. |