> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.beatapi.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.beatapi.io/_mcp/server.

# SEO API

> Keyword data, domain rankings, backlinks, Domain Rating and site traffic with one BeatAPI key — twelve tools that answer in the response, for applications and agents.

Twelve tools give an application or an agent SEO data: how much a keyword is searched and how hard it is to rank for, which keywords and pages bring a domain its search traffic, who competes with it, who links to it, its Domain Rating, and how many visits the site gets. Each tool is one `POST` under `/v1/seo/`, uses your BeatAPI API key and answers in one response. Agents reach the same tools as [capabilities](#agents).

## Quick start

```bash
curl https://api.beatapi.io/v1/seo/keyword-overview \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keywords":["redis cluster"],"country":"us"}'
```

```json
{
  "object": "seo.keyword_overview",
  "request_id": "task_xxx",
  "country": "us",
  "language": "en",
  "results": [
    {
      "keyword": "redis cluster",
      "search_volume": 590,
      "cpc_usd": 15.62,
      "competition": 0.17,
      "keyword_difficulty": 15,
      "intent": "informational",
      "updated_at": "2026-10-16 01:53:48 +00:00",
      "monthly_searches": [{ "year": 2026, "month": 9, "search_volume": 590 }]
    }
  ],
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0183",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

## Authentication

Send your BeatAPI API key on every call as `Authorization: Bearer <key>`; the key works with or without the `sk-` prefix. Create one in [Dashboard → API Keys](https://beatapi.io/dashboard/apikeys). All tools live on `https://api.beatapi.io` and spend your account balance.

## Choose a tool

| You want                                                                         | Tool                 |
| -------------------------------------------------------------------------------- | -------------------- |
| Volume, CPC, difficulty, intent and monthly history of keywords you already have | `keyword-overview`   |
| New keywords around a topic                                                      | `keyword-ideas`      |
| Only the difficulty of a list of keywords                                        | `keyword-difficulty` |
| A domain's search footprint in one market                                        | `domain-overview`    |
| The keywords a domain ranks for, or its position for one keyword                 | `ranked-keywords`    |
| A domain's pages by search traffic                                               | `top-pages`          |
| Domains competing for the same keywords                                          | `competitors`        |
| A domain's link profile in numbers                                               | `backlink-summary`   |
| The links themselves                                                             | `backlinks`          |

For Domain Rating and total site visits, see [Domain Rating and site traffic](#domain-rating-and-site-traffic).

There is no live results-page tool. To check a domain's position for a keyword, call `ranked-keywords` with `contains`. That is the last recorded position, not a live one.

## Country and language

`country` and `language` choose the market: Google search in that country, in that language. The keyword tools, `domain-overview`, `ranked-keywords`, `top-pages` and `competitors` take them. The backlink tools have no market and reject both fields.

| Field      | Default                    | Rule                                                                                                            |
| ---------- | -------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `country`  | `us`                       | Two-letter lowercase code, such as `us`, `gb`, `de` or `jp`. About 90 markets are covered. China (`cn`) is not. |
| `language` | The market's main language | One of the market's languages, such as `es` in `us`, `fr` in `ca` or `zh-TW` in `hk`.                           |

A country or language outside the list is rejected with `400 bad_request`, and the message says what the market has: `language 'de' is not covered in us; use one of: en, es`.

## Limit and offset

`keyword-ideas`, `ranked-keywords`, `top-pages`, `competitors` and `backlinks` return pages of rows.

| Field    | Default | Range                                                                                        |
| -------- | ------- | -------------------------------------------------------------------------------------------- |
| `limit`  | `100`   | 1–1000 rows, 1–999 on `competitors`. Every row returned is billed, so ask for what you need. |
| `offset` | `0`     | 0–10000 rows to skip. Add `limit` to it for the next page.                                   |

These responses repeat `limit` and `offset`, and carry `total`, the rows available in all, when it is known.

## Responses

Every tool answers synchronously: the result is in the response to your `POST`, so there is nothing to poll. Most calls take a few seconds.

| Field                 | Description                                                                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `object`              | The tool, such as `seo.ranked_keywords`.                                                                                                         |
| `request_id`          | Quote it in support requests. `GET /v1/tasks/{id}` with this id returns the same result under `output`, so a lost response is not a lost result. |
| `country`, `language` | The market that was used, on tools that take one.                                                                                                |
| `domain`              | The domain that was used, on tools that take one. A full URL is reduced to its domain and a leading `www.` is dropped.                           |
| `results[]`           | The rows, on tools that return a list. Other tools put their fields at the top level.                                                            |
| `billed_rows`         | The rows this call was charged for.                                                                                                              |
| `has_data`            | On `domain-overview`, `backlink-summary` and the Domain Rating and site-traffic tools. `false` means the domain is not on record.                |
| `usage`               | Ends each response: what the call cost.                                                                                                          |

`usage.price_usd` is the settled charge in US dollars. `billing_unit` is always `request` and `quantity` always `1`; `billed_rows` says how many rows the charge covers.

A number the data does not have is left out of the row, so check for the field before using it. Unknown request fields are rejected with `400 bad_request`: a misspelt field that was silently dropped would answer a different question from the one you asked.

## Keyword tools

### Keyword overview

`POST https://api.beatapi.io/v1/seo/keyword-overview`

Volume, CPC, competition, difficulty, intent and monthly history for keywords you already have. A keyword nobody searches comes back missing, not as zero.

| Field      | Type      | Required | Description                                                                                 |
| ---------- | --------- | -------: | ------------------------------------------------------------------------------------------- |
| `keywords` | string\[] |      Yes | 1–100 keywords of up to 80 characters each. They are lowercased, and duplicates count once. |
| `country`  | string    |       No | See [Country and language](#country-and-language).                                          |
| `language` | string    |       No | See [Country and language](#country-and-language).                                          |

The response is in the [quick start](#quick-start). Fields of each row:

| Field                | Description                                                                     |
| -------------------- | ------------------------------------------------------------------------------- |
| `keyword`            | The keyword.                                                                    |
| `search_volume`      | Monthly searches in this market.                                                |
| `cpc_usd`            | Cost per click of paid ads, in US dollars.                                      |
| `competition`        | Advertiser competition, from 0 to 1.                                            |
| `keyword_difficulty` | How hard the keyword is to rank for, from 0 to 100.                             |
| `intent`             | Main search intent, such as `informational` or `navigational`.                  |
| `updated_at`         | When this keyword's data was last refreshed.                                    |
| `monthly_searches[]` | `year`, `month` and `search_volume` for each month. Only on `keyword-overview`. |

### Keyword ideas

`POST https://api.beatapi.io/v1/seo/keyword-ideas`

Related keywords for your seed keywords, most relevant first. Rows have the fields of `keyword-overview` without `monthly_searches`.

| Field                 | Type      | Required | Description                                        |
| --------------------- | --------- | -------: | -------------------------------------------------- |
| `keywords`            | string\[] |      Yes | 1–20 seed keywords of up to 80 characters each.    |
| `limit`, `offset`     | integer   |       No | See [Limit and offset](#limit-and-offset).         |
| `country`, `language` | string    |       No | See [Country and language](#country-and-language). |

```bash
curl https://api.beatapi.io/v1/seo/keyword-ideas \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keywords":["redis sentinel"],"limit":20}'
```

### Keyword difficulty

`POST https://api.beatapi.io/v1/seo/keyword-difficulty`

Only `keyword_difficulty`, for many keywords at once. It takes the same fields as `keyword-overview`.

```json
{
  "object": "seo.keyword_difficulty",
  "request_id": "task_xxx",
  "country": "us",
  "language": "en",
  "results": [
    { "keyword": "redis cluster", "keyword_difficulty": 15 },
    { "keyword": "redis sentinel", "keyword_difficulty": 2 }
  ],
  "billed_rows": 2,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0186",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

## Domain tools

All four take a `domain` such as `example.com`, plus `country` and `language`.

### Domain overview

`POST https://api.beatapi.io/v1/seo/domain-overview`

A domain's organic and paid search footprint in one market. This is search traffic only, not total visits to the site.

```bash
curl https://api.beatapi.io/v1/seo/domain-overview \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"redis.io","country":"us"}'
```

```json
{
  "object": "seo.domain_overview",
  "request_id": "task_xxx",
  "country": "us",
  "language": "en",
  "domain": "redis.io",
  "has_data": true,
  "organic": {
    "ranking_keywords": 22125,
    "search_traffic_estimate": 108626.58,
    "traffic_value_usd_estimate": 483101.98,
    "rank_distribution": { "1": 987, "2-3": 491, "4-10": 2245, "11-20": 5678, "21-30": 3224, "31-100": 9500 },
    "new": 18919,
    "up": 1048,
    "down": 1105,
    "lost": 4041
  },
  "paid": { "ranking_keywords": 0, "search_traffic_estimate": 0 },
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0183",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

| Field                        | Description                                                                      |
| ---------------------------- | -------------------------------------------------------------------------------- |
| `ranking_keywords`           | Keywords ranking in this market.                                                 |
| `search_traffic_estimate`    | Estimated monthly search traffic.                                                |
| `traffic_value_usd_estimate` | Estimated cost of buying that traffic as ads, in US dollars.                     |
| `rank_distribution`          | Keywords by position: `1`, `2-3`, `4-10`, `11-20`, `21-30` and `31-100`.         |
| `new`, `up`, `down`, `lost`  | Keywords that recently started ranking, moved up, moved down or stopped ranking. |

A domain with nothing on record comes back with `has_data: false` and neither `organic` nor `paid`. That is no data, not zero traffic, and the call is billed.

### Ranked keywords

`POST https://api.beatapi.io/v1/seo/ranked-keywords`

The keywords a domain ranks for in one market, highest search volume first.

| Field                 | Type    | Required | Description                                                                                       |
| --------------------- | ------- | -------: | ------------------------------------------------------------------------------------------------- |
| `domain`              | string  |      Yes | The domain.                                                                                       |
| `contains`            | string  |       No | Only keywords containing this text, up to 80 characters, ignoring case. `%` and `_` are rejected. |
| `limit`, `offset`     | integer |       No | See [Limit and offset](#limit-and-offset).                                                        |
| `country`, `language` | string  |       No | See [Country and language](#country-and-language).                                                |

```bash
curl https://api.beatapi.io/v1/seo/ranked-keywords \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"redis.io","contains":"cluster","limit":1}'
```

```json
{
  "object": "seo.ranked_keywords",
  "request_id": "task_xxx",
  "country": "us",
  "language": "en",
  "domain": "redis.io",
  "limit": 1,
  "offset": 0,
  "total": 108,
  "results": [
    {
      "keyword": "redis cluster",
      "search_volume": 590,
      "cpc_usd": 15.62,
      "competition": 0.17,
      "keyword_difficulty": 15,
      "intent": "informational",
      "updated_at": "2026-10-16 01:53:48 +00:00",
      "position": 1,
      "absolute_position": 2,
      "url": "https://redis.io/docs/cluster/",
      "title": "Redis cluster",
      "position_seen_at": "2026-09-14 04:53:47 +00:00",
      "search_traffic_estimate": 12.35
    }
  ],
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0183",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

Rows add these fields to those of `keyword-ideas`:

| Field                     | Description                                                                               |
| ------------------------- | ----------------------------------------------------------------------------------------- |
| `position`                | Position among the organic results.                                                       |
| `absolute_position`       | Position counting everything on the results page.                                         |
| `url`, `title`            | The page that ranks.                                                                      |
| `position_seen_at`        | When that position was recorded. Positions are a snapshot and can be weeks or months old. |
| `search_traffic_estimate` | Estimated monthly search traffic this keyword brings the page.                            |

### Top pages

`POST https://api.beatapi.io/v1/seo/top-pages`

A domain's pages, highest estimated search traffic first. Fields: `domain`, `limit`, `offset`, `country`, `language`. Each row has a `url` and the [footprint fields](#domain-overview) of that page.

```json
{
  "object": "seo.top_pages",
  "request_id": "task_xxx",
  "country": "us",
  "language": "en",
  "domain": "redis.io",
  "limit": 1,
  "offset": 0,
  "total": 1104,
  "results": [
    {
      "url": "https://redis.io/",
      "ranking_keywords": 22125,
      "search_traffic_estimate": 108626.58,
      "traffic_value_usd_estimate": 483101.98,
      "rank_distribution": { "1": 987, "2-3": 491, "4-10": 2245, "11-20": 5678, "21-30": 3224, "31-100": 9500 },
      "new": 18919,
      "up": 1048,
      "down": 1105,
      "lost": 4041
    }
  ],
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0183",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

### Competitors

`POST https://api.beatapi.io/v1/seo/competitors`

Domains competing with a domain for the same search keywords. Fields: `domain`, `limit`, `offset`, `country`, `language`. The domain itself and the largest general sites, such as youtube.com and wikipedia.org, are left out of the rows, and `total` counts what is left.

```json
{
  "object": "seo.competitors",
  "request_id": "task_xxx",
  "country": "us",
  "language": "en",
  "domain": "redis.io",
  "limit": 1,
  "offset": 0,
  "total": 9,
  "results": [
    {
      "domain": "example.com",
      "shared_keywords": 4706,
      "avg_position": 29.6,
      "ranking_keywords": 22125,
      "search_traffic_estimate": 108626.58
    }
  ],
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0183",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

| Field                     | Description                             |
| ------------------------- | --------------------------------------- |
| `domain`                  | The competing domain.                   |
| `shared_keywords`         | Keywords both domains rank for.         |
| `avg_position`            | Its average position on those keywords. |
| `ranking_keywords`        | Keywords it ranks for in all.           |
| `search_traffic_estimate` | Its estimated monthly search traffic.   |

## Backlink tools

### Backlink summary

`POST https://api.beatapi.io/v1/seo/backlink-summary`

A domain's link profile. The only field is `domain`.

```json
{
  "object": "seo.backlink_summary",
  "request_id": "task_xxx",
  "domain": "redis.io",
  "has_data": true,
  "link_authority": 500,
  "backlinks": 920428,
  "referring_domains": 32647,
  "referring_main_domains": 28596,
  "referring_pages": 748383,
  "referring_ips": 19191,
  "broken_backlinks": 8482,
  "broken_pages": 2773,
  "spam_score": 28,
  "first_seen": "2019-01-15 22:56:44 +00:00",
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0352",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

| Field                                         | Description                                                                 |
| --------------------------------------------- | --------------------------------------------------------------------------- |
| `link_authority`                              | Authority of the link profile, from 0 to 1000 on this data set's own scale. |
| `backlinks`                                   | Links found.                                                                |
| `referring_domains`, `referring_main_domains` | Domains linking to it, and the same with subdomains counted as one.         |
| `referring_pages`, `referring_ips`            | Pages and IP addresses linking to it.                                       |
| `broken_backlinks`, `broken_pages`            | Links that lead nowhere, and the domain's pages that no longer work.        |
| `spam_score`                                  | Spam score of the profile. Higher means more likely spam.                   |
| `first_seen`                                  | When a link to the domain was first seen.                                   |

`has_data` is `false` when the domain is not on record, with no other fields, and the call is billed.

### Backlinks

`POST https://api.beatapi.io/v1/seo/backlinks`

The links pointing at a domain, strongest first.

| Field             | Type    | Required | Description                                                                     |
| ----------------- | ------- | -------: | ------------------------------------------------------------------------------- |
| `domain`          | string  |      Yes | The domain.                                                                     |
| `one_per_domain`  | boolean |       No | Defaults to `true`: one link per linking domain. Set `false` to get every link. |
| `limit`, `offset` | integer |       No | See [Limit and offset](#limit-and-offset).                                      |

```json
{
  "object": "seo.backlinks",
  "request_id": "task_xxx",
  "domain": "redis.io",
  "limit": 1,
  "offset": 0,
  "total": 28843,
  "results": [
    {
      "source_url": "https://blog.example.com/caching",
      "source_domain": "blog.example.com",
      "source_title": "Caching notes",
      "source_authority": 493,
      "source_country": "CZ",
      "target_url": "https://redis.io/",
      "anchor": "Redis",
      "link_type": "anchor",
      "dofollow": true,
      "spam_score": 3,
      "first_seen": "2025-04-26 07:12:33 +00:00",
      "last_seen": "2026-06-18 19:14:30 +00:00",
      "is_new": false,
      "is_lost": false,
      "is_broken": false
    }
  ],
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.0352",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

| Field                                         | Description                                             |
| --------------------------------------------- | ------------------------------------------------------- |
| `source_url`, `source_domain`, `source_title` | The page that links.                                    |
| `source_authority`                            | Link authority of the linking domain, from 0 to 1000.   |
| `source_country`                              | Country of the linking domain.                          |
| `target_url`, `anchor`                        | The page linked to, and the link text.                  |
| `link_type`                                   | Kind of link, such as `anchor`.                         |
| `dofollow`                                    | Whether the link passes authority.                      |
| `spam_score`                                  | Spam score of this link. Higher means more likely spam. |
| `first_seen`, `last_seen`                     | When the link was first and last seen.                  |
| `is_new`, `is_lost`, `is_broken`              | Whether the link is new, has gone, or leads nowhere.    |

## Reading the numbers

* **Search traffic.** `search_traffic_estimate` is estimated search traffic, not total visits to a site.
* **Estimates.** Traffic and value figures are estimates, not measurements of the site.
* **Link authority.** `link_authority` runs from 0 to 1000 on this data set's own scale.
* **Keyword difficulty** is this data set's own scale. Do not compare it with another tool's score.
* **Missing is not zero.** A number that is missing is unknown, never zero.
* **No live results page.** There is no live results-page tool. To check a domain's position for a keyword, use [`ranked-keywords`](#ranked-keywords) with `contains`. That is the last recorded position, not a live one.
* **Domain Rating and site traffic** have their own notes in [Reading these numbers](#reading-these-numbers).

## Billing

| Tool                                                                                                                        | Per call | Per row returned |
| --------------------------------------------------------------------------------------------------------------------------- | -------: | ---------------: |
| `keyword-overview`, `keyword-ideas`, `keyword-difficulty`, `domain-overview`, `ranked-keywords`, `top-pages`, `competitors` |  \$0.018 |         \$0.0003 |
| `backlink-summary`, `backlinks`                                                                                             |  \$0.035 |         \$0.0002 |

Domain Rating and site traffic have [their own prices](#prices-of-these-tools).

* `domain-overview` and `backlink-summary` return one record, which counts as one row, and none when `has_data` is `false`. A call that returns 100 rows of `ranked-keywords` costs \$0.048.
* A call that finds nothing still costs its per-call part. A rejected request (`400`) costs nothing, and neither does a failed call (`503`).
* Before a call runs, your balance must cover the most it can cost: every row of `limit`, or every keyword sent. You are then charged only for what came back.
* Send an `Idempotency-Key` header when your client may retry the same request. Reusing a key with a different body is rejected with `409 idempotency_conflict`.

## Agents

There is no separate MCP tool for SEO data. On the BeatAPI MCP endpoint `https://beatapi.io/mcp`, agents use `capabilities_search`, `capabilities_inspect` and `capabilities_run` with these references:

| Reference                     | Tool                 |
| ----------------------------- | -------------------- |
| `data:seo.keyword_overview`   | `keyword-overview`   |
| `data:seo.keyword_ideas`      | `keyword-ideas`      |
| `data:seo.keyword_difficulty` | `keyword-difficulty` |
| `data:seo.domain_overview`    | `domain-overview`    |
| `data:seo.ranked_keywords`    | `ranked-keywords`    |
| `data:seo.top_pages`          | `top-pages`          |
| `data:seo.competitors`        | `competitors`        |
| `data:seo.backlink_summary`   | `backlink-summary`   |
| `data:seo.backlinks`          | `backlinks`          |

The references for Domain Rating and site traffic are [listed with those tools](#references-for-agents).

Inspect shows the input schema and the current price. `input` is the request body of the tool, and Run answers with the result:

```json
{ "reference": "data:seo.ranked_keywords", "operation": "start", "input": { "domain": "redis.io", "contains": "cluster", "limit": 20 } }
```

The same three calls work over REST at `POST /v1/capabilities/search`, `/v1/capabilities/inspect` and `/v1/capabilities/run`. See [Capabilities API](/capabilities).

## Errors

Errors use the standard BeatAPI error envelope, with `retryable` on every error. See [Errors](/errors) for the retry policy.

```json
{
  "error": {
    "code": "bad_request",
    "message": "country 'cn' is not covered; use a two-letter code such as us, gb, de or jp (China is not covered)",
    "retryable": false,
    "request_id": "req_abc123"
  }
}
```

| HTTP status | Code                                                                  | Meaning                                                                                                                           |
| ----------: | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|         400 | `bad_request`                                                         | An invalid or unknown field, a keyword or domain that is not valid, or a market that is not covered. The message names the field. |
|         401 | `missing_api_key` / `invalid_api_key`                                 | No key was sent, or the key is wrong, expired, disabled, or exhausted.                                                            |
|         402 | `insufficient_credits`                                                | Insufficient balance: the balance is zero or below the price of the call.                                                         |
|         403 | `forbidden`                                                           | The key or account is not permitted to make this call.                                                                            |
|         409 | `idempotency_conflict`                                                | The `Idempotency-Key` was reused with a different body.                                                                           |
|         429 | `rate_limit_exceeded`                                                 | Too many requests. Wait `retry_after_seconds`.                                                                                    |
|         500 | `internal_error`                                                      | Unexpected internal error. Retryable.                                                                                             |
|         503 | `processing_unavailable`, `processing_timeout` or `processing_failed` | SEO data is temporarily unavailable, or the call took too long. Retry with backoff.                                               |

A `503` is never charged. BeatAPI does not answer `502`.

## Domain Rating and site traffic

Three more tools work the same way: same key, same answer in the response, same `usage` and errors. They take a `domain` and have no market, so `country` and `language` are rejected. There is no `limit` or `offset`.

| You want                                                             | Tool                    |
| -------------------------------------------------------------------- | ----------------------- |
| The 0–100 score SEO teams compare domains by                         | `domain-rating`         |
| That score month by month                                            | `domain-rating-history` |
| Total visits to a site, where they come from and how visitors behave | `site-traffic`          |

### Domain Rating

`POST https://api.beatapi.io/v1/seo/domain-rating`

```bash
curl https://api.beatapi.io/v1/seo/domain-rating \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"redis.io"}'
```

```json
{
  "object": "seo.domain_rating",
  "request_id": "task_xxx",
  "domain": "redis.io",
  "has_data": true,
  "domain_rating": 90,
  "link_rank": 1927,
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.005",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

`domain_rating` runs from 0 to 100. `link_rank` is the domain's global rank by links; lower is stronger.

### Domain Rating history

`POST https://api.beatapi.io/v1/seo/domain-rating-history`

```json
{
  "object": "seo.domain_rating_history",
  "request_id": "task_xxx",
  "domain": "redis.io",
  "has_data": true,
  "results": [
    { "month": "2025-12", "domain_rating": 89 },
    { "month": "2026-01", "domain_rating": 89 },
    { "month": "2026-02", "domain_rating": 89 },
    { "month": "2026-03", "domain_rating": 89 },
    { "month": "2026-04", "domain_rating": 89 },
    { "month": "2026-05", "domain_rating": 89 },
    { "month": "2026-06", "domain_rating": 90 },
    { "month": "2026-07", "domain_rating": 90 },
    { "month": "2026-08", "domain_rating": 90 },
    { "month": "2026-09", "domain_rating": 90 },
    { "month": "2026-10", "domain_rating": 90 }
  ],
  "billed_rows": 11,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.044",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

An unknown domain returns `has_data: false`, an empty `results` and `billed_rows: 1`.

### Site traffic

`POST https://api.beatapi.io/v1/seo/site-traffic`

Estimated total visits to a website from all channels, with sources, countries, rank and engagement.

| Field    | Type    | Required | Description                                    |
| -------- | ------- | -------: | ---------------------------------------------- |
| `domain` | string  |      Yes | The domain.                                    |
| `months` | integer |       No | Months of history: `3` (default), `6` or `12`. |

```bash
curl https://api.beatapi.io/v1/seo/site-traffic \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"redis.io","months":3}'
```

```json
{
  "object": "seo.site_traffic",
  "request_id": "task_xxx",
  "domain": "redis.io",
  "months": 3,
  "has_data": true,
  "site_visits_estimate": 734682,
  "monthly_visits": [
    { "month": "2026-07", "visits": 827501 },
    { "month": "2026-08", "visits": 789909 },
    { "month": "2026-09", "visits": 734682 }
  ],
  "global_rank": 66213,
  "country_rank": { "country": "IN", "rank": 19016 },
  "category": "computers_electronics_and_technology/programming_and_developer_software",
  "category_rank": 634,
  "bounce_rate": 0.4254,
  "pages_per_visit": 3.3174,
  "avg_visit_seconds": 116.2764,
  "traffic_sources": {
    "direct": 0.3293,
    "search": 0.4836,
    "paid_search": 0.0072,
    "social": 0.0435,
    "paid_social": 0.0026,
    "referrals": 0.0992,
    "mail": 0.0114,
    "display_ads": 0.0035,
    "affiliate": 0,
    "gen_ai": 0.0197
  },
  "top_countries": [{ "country": "IN", "share": 0.1995 }],
  "top_keywords": [{ "keyword": "redis", "search_volume": 193980, "cpc_usd": 2.78 }],
  "title": "Redis - Real-time data for agents & apps",
  "description": "Developers love Redis. Unlock the full potential of the Redis database with Redis Enterprise and start building blazing fast apps.",
  "billed_rows": 1,
  "usage": {
    "billing_unit": "request",
    "quantity": 1,
    "price_usd": "0.005",
    "price_version": "web_3f9a1c2b7d10"
  }
}
```

| Field                                  | Description                                                           |
| -------------------------------------- | --------------------------------------------------------------------- |
| `site_visits_estimate`                 | Estimated visits in the latest month.                                 |
| `monthly_visits[]`                     | `month` and `visits` for each month.                                  |
| `global_rank`                          | Rank among all sites. Lower is bigger.                                |
| `country_rank`                         | Rank in one country: `country` and `rank`.                            |
| `category`, `category_rank`            | The site's category and its rank there.                               |
| `bounce_rate`                          | Share of visits that leave after one page, from 0 to 1.               |
| `pages_per_visit`, `avg_visit_seconds` | Pages viewed per visit and the average visit length in seconds.       |
| `traffic_sources`                      | Share of visits from each channel, from 0 to 1.                       |
| `top_countries[]`                      | Countries sending the most visits, each with its `share` from 0 to 1. |
| `top_keywords[]`                       | Keywords bringing search visits, with `search_volume` and `cpc_usd`.  |
| `title`, `description`                 | The site's own title and description.                                 |

### Reading these numbers

* **Two kinds of traffic.** `search_traffic_estimate` is estimated search traffic; `site_visits_estimate` is total visits from all channels. They are different measures: never add or compare them. Visits are not unique visitors.
* **Two authority scores.** `link_authority` runs from 0 to 1000 on this data set's own scale; `domain_rating` runs from 0 to 100. They cannot be converted to each other.
* **Not on record.** `has_data: false` means the domain is not on record, which differs from a rating or traffic of zero: a new site can have a rating of `0` and still have a `link_rank`.

### Prices of these tools

| Tool                      |                      Price |
| ------------------------- | -------------------------: |
| `domain-rating`           |           \$0.005 per call |
| `domain-rating-history`   | \$0.004 per month returned |
| `site-traffic`, 3 months  |                    \$0.005 |
| `site-traffic`, 6 months  |                     \$0.01 |
| `site-traffic`, 12 months |                    \$0.015 |

* `domain-rating` and `site-traffic` are billed when `has_data` is `false`. `domain-rating-history` for an unknown domain returns no months and costs one, \$0.004.
* A rejected request (`400`) and a failed call (`503`) cost nothing.
* Before `domain-rating-history` runs, your balance must cover 24 months of history. You are then charged only for what came back.

### References for agents

| Reference                        | Tool                    |
| -------------------------------- | ----------------------- |
| `data:seo.domain_rating`         | `domain-rating`         |
| `data:seo.domain_rating_history` | `domain-rating-history` |
| `data:seo.site_traffic`          | `site-traffic`          |