API reference
Index check
Is a URL in Google's index? Query page 1 of `site:<url>` and track when results are fresh.
Endpoint overview
The index check endpoints answer whether each URL appears in Google's index by running a site: crawl on page 1 only (no AI Overview, no feature capture). Use /index/v1/check to start new checks or refresh stale ones; /index/v1/status to poll without resubmitting. Both return the same response format.
Start or refresh a check
/index/v1/checkCheck whether each URL is in Google's index. Starts a crawl for URLs never checked before, older than 24 hours, or when force: true is set. Reuses results from the last 24 hours unless forced. A failed crawl is automatically retried (up to 3 rounds).
curl -X POST https://serp-api.hoangha.shop/index/v1/check \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/page",
"https://example.com/blog/post",
"https://example.org/article"
],
"force": false,
"gl": "vn",
"hl": "vi"
}'{
"isSuccess": true,
"content": [
{
"url": "https://example.com/page",
"status": "INDEXED",
"reason": null,
"lastCheckedAt": "2026-10-06T14:23:45Z",
"submitted": true
},
{
"url": "https://example.com/blog/post",
"status": "NOT_INDEXED",
"reason": "NO_RESULTS",
"lastCheckedAt": "2026-10-06T10:15:30Z",
"submitted": false
},
{
"url": "https://example.org/article",
"status": "PENDING",
"reason": null,
"lastCheckedAt": null,
"submitted": true
}
]
}| Field | Type | Description |
|---|---|---|
| urlsrequired | array | List of URLs to check. 1–100 URLs per request. Each URL must be a valid http(s) URL with a hostname, up to 395 characters. Duplicates (after normalisation) are deduplicated. |
| force | boolean | Re-crawl even if the URL was checked in the last 24 hours. Default false. Does not count as a new submission when a fresh result already exists. |
| gl | string | Country code, e.g. "vn", "us", "gb". Geo-targets the search. Passed to the `site:` crawl. Optional. |
| hl | string | Interface language, e.g. "vi", "en". Language-targets the search interface. Passed to the `site:` crawl. Optional. |
Poll without submitting
/index/v1/statusLook up the current index status for each URL without starting a new crawl or refreshing stale results. Useful for polling in a loop. A failed crawl is still retried up to 3 rounds while you poll.
curl -X POST https://serp-api.hoangha.shop/index/v1/status \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/page",
"https://example.com/blog/post"
]
}'The response format is identical to /check.
| Field | Type | Description |
|---|---|---|
| urlsrequired | array | List of URLs to look up. 1–100 URLs per request. Same format and validation as /check. |
Response statuses
INDEXED
The URL appears in Google's index. A result from page 1 matches the URL after normalisation (scheme, www., trailing slash, fragment, and host case are ignored). lastCheckedAt is the timestamp when the crawl finished.
NOT_INDEXED
The URL is not in Google's index. reason explains why:
NO_RESULTS: thesite:search returned no results at all.ONLY_SIMILAR_URLS: results exist but none match this URL. (Thesite:operator is a prefix match, so related URLs may appear.)
lastCheckedAt is the timestamp when the crawl finished.
PENDING
A crawl is queued, running, or being retried. reason is null unless:
CRAWL_FAILED: the crawl failed 3 times and all retries are exhausted. Call /check again to restart it — the restart is free (no charge, noforceneeded).
lastCheckedAt is null while in this state.
NOT_SUBMITTED
The URL has never been checked, or the cached state expired (7 days). Use /check to start a crawl. lastCheckedAt is null.
Caching and freshness
Results are cached for 24 hours from the finish time of the crawl. A subsequent check returns the cached result without a new crawl.
- On
/check: if a result is less than 24 hours old, it is returned andsubmitted: false(no charge). Setforce: trueto crawl fresh. - On
/status: only reads cached state; never submits a new crawl. - A crawl that fails is retried automatically (no manual action needed) and does not count as a new submission.
Typical usage
/check, then poll with /status to avoid accidental resubmissions. This way you pay once per URL per 24 hours.URL normalisation
URLs are compared after normalisation. The following variations are treated as the same page:
http://vshttps://example.comvswww.example.comexample.com/pagevsexample.com/page/(trailing slash)example.com/page#section(fragments are ignored)Example.comvsexample.com(host case is ignored)
Query strings and path case are NOT normalised and are compared exactly.
Error handling
| Field | Type | Description |
|---|---|---|
| 400 | error | Invalid request: missing or empty urls list, more than 100 URLs, a URL that is not http(s), or a URL longer than 395 characters. |
| 401 | error | Missing or invalid API key. |
| 402 | error | Insufficient credits. Only on serp-portal (portal bills 1000 credits per new check). |
| 429 | error | Rate limit exceeded or quota backpressure. On /check when no new crawls can be queued. Retry after a delay. |
Billing (serp-portal)
On the serp-portal website, index checks consume credits:
- Each URL that starts a NEW crawl (not from cache, not already in flight) costs 1,000 credits.
- Polling for results and retrying a failed crawl are free (no charge).
- Duplicate URLs in one request are deduplicated; you pay once per unique URL.
Implementation example
async function checkIndexStatus(urls, apiKey) {
const maxRetries = 30;
const delayMs = 2000;
// Start checks
const response = await fetch('https://serp-api.hoangha.shop/index/v1/check', {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ urls })
});
const results = await response.json();
console.log('Submitted:', results.content.filter(r => r.submitted).map(r => r.url));
// Poll status
for (let i = 0; i < maxRetries; i++) {
await new Promise(resolve => setTimeout(resolve, delayMs));
const statusRes = await fetch('https://serp-api.hoangha.shop/index/v1/status', {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ urls })
});
const status = await statusRes.json();
const indexed = status.content.filter(r => r.status === 'INDEXED');
const notIndexed = status.content.filter(r => r.status === 'NOT_INDEXED');
const pending = status.content.filter(r => r.status === 'PENDING');
console.log(`Indexed: ${indexed.length}, Not indexed: ${notIndexed.length}, Pending: ${pending.length}`);
if (pending.length === 0) {
return status.content;
}
}
throw new Error('Timeout waiting for index check to complete');
}