> Searlo API Docs — Rank Checks
> Source: https://searlo.tech/docs#rankings
> Full docs: https://searlo.tech/docs/full.md

# Rank Checks

Where a domain ranks for a keyword, read from a live Google results page: the position, the page that ranks and the domains above it. Every check is stored, so the history builds up from your own checks.

### Endpoints

```
POST /rankings/check
POST /rankings/batch
GET  /rankings/history
```

**Credits:** 3 per 10 results of `depth`, because each 10 results is one results page read. The default `depth` of 100 costs 30 credits; `depth` 10 costs 3. A batch costs that for each distinct keyword. History is free.

### Check Parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `keyword` | string | Yes | 1–400 chars |
| `domain` | string | Yes | 3–253 chars. Reduced to its registrable domain (`blog.example.com` becomes `example.com`): any subdomain counts as a match, and `url` says which page ranked |
| `depth` | number | No | How many results to search (10–100, default: 100), read in pages of 10. `depth` 25 reads 3 pages and costs 9 |
| `gl` | string | No | Country, 2 letters (default: `us`) |
| `hl` | string | No | Language, 2–5 chars (default: `en`) |
| `device` | string | No | `desktop` or `mobile` (default: `desktop`). Positions are read from Google's desktop results page for both today; `device` is recorded with the check as asked |
| `location` | string | No | A country, by name, two-letter code or `uule` (max 300 chars). Applied as `gl` and echoed in the result as `location`. A city or state returns `400` `CITY_LOCATION_UNAVAILABLE` before any credit is taken, for a batch too (see [Location](#full-serp)) |
| `cache` | boolean | No | Reuse a matching cached results page (default: true) |

### Check Response

```json
{
  "schema": "ranking-check.v1",
  "result": {
    "keyword": "project management software",
    "domain": "example.com",
    "position": 4,
    "url": "https://www.example.com/features",
    "title": "Project management features | Example",
    "found": true,
    "search_depth": 30,
    "organic_count": 30,
    "pages_fetched": 3,
    "gl": "US",
    "hl": "en",
    "device": "desktop",
    "serp_features": ["organic", "ads", "people_also_ask", "related_searches", "sitelinks"],
    "competitors_above": ["asana.com", "monday.com", "clickup.com"],
    "checked_at": "2026-09-29T10:00:00.000Z",
    "cached": false
  },
  "credits": { "used": 9 }
}
```

- `found: false` with `position: null` means the domain was not in the top `search_depth` results. `search_depth` is what was asked for, so a short results page does not change what `found: false` means.
- `competitors_above` lists up to 20 domains ranking above yours, top first. When the domain was not found, it lists the domains on the page.
- `serp_features` uses the [SERP features](#full-serp) vocabulary. A rank check skips the AI Overview, so `ai_overview` never appears here.
- A failed results-page read returns `429` (`AT_CAPACITY`) or `503` (`UPSTREAM_UNAVAILABLE`), as on `/serp`, and is refunded. An unparseable `domain` is `400` `INVALID_DOMAIN`.

### Batch Endpoint

```
POST /rankings/batch
```

One domain, up to 20 keywords: `keywords` (1–20, each 1–400 chars) and `domain`, plus the same `depth`, `gl`, `hl`, `device`, `location` and `cache` fields as a check. Each keyword is a separate check and a separate history entry. `meta.max_sync_batch` reports the cap in force.

```json
{
  "schema": "ranking-batch.v1",
  "results": [
    { "success": true, "keyword": "project management software", "domain": "example.com", "position": 4, "url": "https://www.example.com/features", "title": "Project management features | Example", "found": true, "search_depth": 30, "organic_count": 30, "pages_fetched": 3, "gl": "US", "hl": "en", "device": "desktop", "serp_features": ["organic", "ads"], "competitors_above": ["asana.com"], "checked_at": "2026-09-29T10:00:00.000Z", "cached": false },
    { "keyword": "kanban board software", "success": false, "code": "UPSTREAM_UNAVAILABLE", "retryable": true, "message": "Google SERP is temporarily unavailable. Please try again shortly." }
  ],
  "credits": { "used": 18 },
  "meta": { "domain": "example.com", "requested": 2, "distinct": 2, "measured": 1, "ranking": 1, "failed": 1, "max_sync_batch": 20 }
}
```

A keyword that fails inside a `200` batch is still billed.

### History Endpoint

```
GET /rankings/history
```

**Credits:** Free (0).

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `domain` | string | Yes | The domain you checked |
| `keyword` | string | No | One keyword. Set it to get a single series and `change` |
| `gl` | string | No | 2-letter country |
| `device` | string | No | `desktop` or `mobile` |
| `from` | string | No | ISO date: earliest `checked_at` |
| `to` | string | No | ISO date: latest `checked_at` |
| `limit` | number | No | 1–1000 (default: 100), newest first |

```json
{
  "schema": "ranking-history.v1",
  "series": [
    { "keyword": "project management software", "domain": "example.com", "gl": "US", "hl": "en", "device": "desktop", "position": 4, "url": "https://www.example.com/features", "search_depth": 30, "serp_features": ["organic", "ads"], "checked_at": "2026-09-29T10:00:00.000Z" },
    { "keyword": "project management software", "domain": "example.com", "gl": "US", "hl": "en", "device": "desktop", "position": 6, "url": "https://www.example.com/features", "search_depth": 30, "serp_features": ["organic", "ads"], "checked_at": "2026-09-28T10:00:00.000Z" }
  ],
  "change": { "delta": 2, "from": 6, "to": 4 },
  "credits": { "used": 0 },
  "meta": { "domain": "example.com", "returned": 2, "limit": 100 }
}
```

`change` appears when `keyword` is set and the series has at least two checks. A positive `delta` means the domain moved up. When one of the two checks found nothing, `delta` is `null` and `event` is `entered` or `dropped_out`. History holds only the checks you ran (batch rows and async `rankings/check` tasks included). It is not a back-filled index, and it is kept for a configurable retention period.

## Code Examples

### cURL

```bash
# Top 30 results, 9 credits
curl -X POST "https://api.searlo.tech/api/v1/rankings/check" \
  -H "x-api-key: sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "keyword": "project management software", "domain": "example.com", "depth": 30, "gl": "us" }'

# The stored series for that keyword (free)
curl "https://api.searlo.tech/api/v1/rankings/history?domain=example.com&keyword=project+management+software" \
  -H "x-api-key: sk_your_api_key"
```

### JavaScript

```javascript
const API = "https://api.searlo.tech/api/v1";
const headers = { "x-api-key": process.env.SEARLO_API_KEY, "Content-Type": "application/json" };

const res = await fetch(`${API}/rankings/batch`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    domain: "example.com",
    keywords: ["project management software", "kanban board software", "gantt chart tool"],
    depth: 20, // 2 pages per keyword = 6 credits per keyword
    gl: "us",
  }),
});
const { results, meta, credits } = await res.json();

for (const r of results) {
  if (!r.success) console.log(r.keyword, "not measured:", r.message);
  else console.log(r.keyword, r.found ? `#${r.position} ${r.url}` : `not in top ${r.search_depth}`);
}
console.log(`${meta.ranking}/${meta.distinct} ranking, ${credits.used} credits`);
```

### Python

```python
import os
import requests

API = "https://api.searlo.tech/api/v1"
HEADERS = {"x-api-key": os.environ["SEARLO_API_KEY"]}

check = requests.post(
    f"{API}/rankings/check",
    json={"keyword": "project management software", "domain": "example.com", "depth": 10},
    headers=HEADERS,
    timeout=120,
).json()["result"]
print(check["found"], check["position"], check["url"], check["competitors_above"][:3])

history = requests.get(
    f"{API}/rankings/history",
    params={"domain": "example.com", "keyword": "project management software", "limit": 30},
    headers=HEADERS,
).json()
print(history["change"])
for row in history["series"]:
    print(row["checked_at"], row["position"])
```


---
*From https://searlo.tech/docs — see full docs at https://searlo.tech/docs/full.md*