docs / endpoint
POST /v1/domains/gap
Keyword gap between two domains: opportunity, shared, or unique rankings, volume-enriched.
Credits: 15 credits per call.
Timeout: 60s
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
domains |
array | required | — | Put your own domain first -- every mode is defined relative to domains[0]. Array of string. Max 2 items. |
mode |
string | optional | "gap" |
gap: keywords domains[1] ranks for that domains[0] does not. shared: both rank. unique: only domains[0] ranks. Optional, default "gap". |
engine |
string | optional | "google" |
Optional, default "google". Same enum as /v1/search. |
location |
integer | 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 -- sets how far back in the archive the comparison reaches. |
limit |
integer | optional | 100 |
Optional, default 100. 1-1000. |
offset |
integer | optional | 0 |
Optional, default 0. 0-10000. |
Request
→ requestPOST /v1/domains/gap
POST /v1/domains/gap HTTP/1.1
Authorization: Bearer sof_live_your_key_here
Content-Type: application/json
{
"domains": [
"semrush.com",
"ahrefs.com"
],
"mode": "gap",
"engine": "google",
"location": 2840,
"language": "en",
"device": "desktop",
"limit": 100,
"offset": 0
}
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": "doma_4iwnb3n4nfvvsw55wnpwp2fm",
"request_id": "req_5ynm5drlkzbkde52tuuqp3yloy",
"object": "domain_gap",
"created_at": "2026-08-09T07:19:28Z",
"elapsed_ms": 180,
"cache": "miss",
"credits": {
"charged": 15,
"balance": 9857
},
"data": {
"mode": "gap",
"domains": [
"semrush.com",
"ahrefs.com"
],
"items": [
{
"keyword": "search engine optimization",
"volume": 18100,
"ranks": [
null,
{
"rank": 27,
"url": "https://ahrefs.com/blog/what-is-seo/",
"date": "2026-06-28"
}
]
},
"…"
],
"totals": [
2834,
1315
],
"matched_count": 409,
"coverage": {
"from": "2026-06-28",
"observations": 409
},
"limit": 100,
"offset": 0
}
}
Response fields
What each field in data (above) means.
| Field | Description |
|---|---|
data.mode |
Comparison mode that was applied: gap, shared, or unique. |
data.domains |
The two domains you compared, in the order you sent them. |
data.items |
One row per matching keyword, up to limit. |
data.items[].keyword |
The keyword. |
data.items[].volume |
Search volume for the keyword. |
data.items[].ranks |
Two-element array positionally aligned to domains: ranks[0] is domains[0]'s rank, ranks[1] is domains[1]'s rank on this keyword. Each entry is null (that domain doesn't rank) or {rank, url, date}. |
data.totals |
Two-element array positionally aligned to domains: total ranked-keyword count for each domain. |
data.matched_count |
Total keywords matching mode, before limit/offset truncation. |
data.coverage |
How much of the rankings archive this comparison drew from. |
data.coverage.from |
Earliest observation date among the returned items, floor-clamped to date_from. |
data.coverage.observations |
Same as matched_count. |
data.limit |
Page size actually used (echoes the request). |
data.offset |
Pagination offset actually used (echoes the request). |