Data API for 192,808,842 domain ratings

Two endpoints. One rates a list of up to 100 domains in a single request. The other lists every parquet file your subscription covers, each with a short-lived signed URL you can fetch straight from object storage, so a scheduled job can pick up each new release without anyone clicking anything.

The ratings endpoint is free to try: any account can create a key, and a free key answers 25 requests a day, up to 100 domains each. That is 2,500 domains a day, with no card and no trial clock.

Would rather ask than write code? An AI assistant can connect over MCP by signing in, within the same limits. How to connect one.

Get a free API key

Base URL https://checkdomainrating.com/api/v1 · OpenAPI description

Bulk data subscription

$125 a month

Billed $1,499 USD once a year

A full year of access, including every release published while it runs.

It covers both parts: the monthly files to keep, and the data API for looking up individual ratings, up to 100 domains a request.

  • Every monthly release back to 2017, and every new one
  • Over 200 million domains, scored again every month
  • Files you keep, plus an API to fetch them
  • An API for individual ratings, 100 domains a request
  • No usage limits and no charge per person

Not ready? try the free sample first.

Monthly plan

$149 a month

The data API and the newest monthly file. Paid by card each month. Stop any time.

  • The newest monthly file, with every domain we rate
  • Each new file as soon as it comes out
  • The data API: 5,000 requests a day, up to 100 domains each
  • No charge per person
Pay monthly

Not right for you? Email us within 14 days of your first payment and we will give the money back.

Authentication

Generate a key on your account page and send it as a bearer token. Any account can create one, free. No account yet? Get a free key with just your email address.

Authorization: Bearer cdr_AbCdEf123456_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • • A key is shown once, when you create it. We store only a bcrypt hash, so we cannot show it again or recover it. If it is lost, generate another.
  • • Generating a new key revokes the old one immediately.
  • • The key goes in the header only. We do not accept it in a query string, because that would put it in access logs, proxy logs and shell history.
  • • The key identifies your account. The ratings endpoint works with any key, within the free allowance if there is no subscription behind it. The downloads endpoint needs an active subscription.
GET /api/v1/downloads

Every mirrored release, newest first. URLs are signed per request, so call this each time rather than storing the links.

Query parameters

NameTypeMeaning
expires_in integer How long the signed URLs stay valid, in seconds. 3600 by default, 60 to 86400 allowed. The clock starts when the link is issued, not when the download starts, so a long transfer that began in time is not cut off.

Example

curl -sS https://checkdomainrating.com/api/v1/downloads \
  -H "Authorization: Bearer $CDR_API_KEY"
{
  "generated_at": "2026-09-05T06:40:00Z",
  "current_month": "2025-12",
  "subscription_ends_at": "2027-07-05T09:12:44Z",
  "count": 2,
  "downloads": [
    {
      "month": "2025-12",
      "filename": "2025-12.parquet",
      "size_bytes": 24696061952,
      "domain_count": 248113004,
      "etag": "\"9f86d081884c7d65\"",
      "computed_at": "2026-01-06T09:30:00Z",
      "url": "https://checkdomainrating.com/...signed...",
      "url_expires_at": "2026-09-05T07:40:00Z"
    }
  ]
}

Response fields

FieldTypeMeaning
generated_at string When this response was built, and when the URLs below start counting.
current_month string or null The monthly release the free checker is currently answering from.
subscription_ends_at string or null When your access ends. Useful for warning yourself early.
count integer How many entries are in downloads.
downloads array Newest first.
downloads[].month string The month this release is named after: the last month of its crawl window.
downloads[].filename string What to save it as. Also the object's key in our bucket.
downloads[].size_bytes integer Exact size of the file, so you can check a download finished.
downloads[].domain_count integer or null How many domains the release rates, which is how many rows the file has.
downloads[].etag string or null The object's entity tag, for checking a download you already hold.
downloads[].computed_at string or null When the ratings were computed, not when we mirrored them.
downloads[].url string A signed GET URL. Follow it with no headers and no credentials.
downloads[].url_expires_at string After this the URL is dead; ask for the list again.

Errors

Every error is JSON with an error code and a message.

StatusCodeWhen
400 bad_request expires_in was not a number between 60 and 86400, or the domains list was missing or empty.
401 unauthenticated The Authorization header was missing, malformed or wrong.
403 subscription_inactive The key is good but has no active subscription behind it. Only the downloads list needs one.
422 too_many_domains More than 100 domains were sent in one request.
429 daily_limit_reached A free key has used its 25 requests for the day. Retry-After counts down to midnight UTC.
429 rate_limited Too many requests. Retry-After says how long to wait.

Rate limits

Every key: 60 requests a minute and 5,000 a day, counted across both endpoints. A 429 carries a Retry-After header in seconds. At 100 domains a request that is half a million domains a day; for more than that, the monthly file has all of them and no limit at all.

Free keyWith the subscription
Ratings requests a day 25 5,000
Domains a day 2,500 500,000
Downloads endpoint and the monthly files back to 2017 No Yes

A free key's day runs midnight to midnight UTC, and only requests that were answered count. A request refused for its body (a 400 or a 422) costs nothing. Every answer to a free key carries these headers, so a script can pace itself instead of finding out at the 429:

  • • X-RateLimit-Limit: Requests a free key may make in a UTC day.
  • • X-RateLimit-Remaining: How many of those are left today.
  • • X-RateLimit-Reset: When the count starts again, as a Unix timestamp: the next midnight UTC.
POST /api/v1/ratings

Up to 100 domains in one request, answered in the order you sent them. Each entry is reduced to its apex domain first, so https://www.bbc.co.uk/news, www.bbc.co.uk:443 and bbc.co.uk are the same lookup. Repeats are answered once, so the answer can be shorter than the list. Nothing about the list is logged, cached or stored.

Request body

JSON, with domains as either a list of entries or one string with them separated by newlines, commas, spaces, tabs or semicolons. The second form takes a pasted file without splitting it first.

Example

curl -sS https://checkdomainrating.com/api/v1/ratings \
  -H "Authorization: Bearer $CDR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["bbc.co.uk", "https://www.gov.uk/page", "not a domain"]}'
{
  "generated_at": "2026-09-05T06:40:00Z",
  "month": "2025-12",
  "count": 3,
  "ratings": [
    { "input": "bbc.co.uk", "domain": "bbc.co.uk", "rating": 91 },
    { "input": "https://www.gov.uk/page", "domain": "www.gov.uk", "rating": 88 },
    { "input": "not a domain", "domain": null, "rating": null }
  ]
}

Response fields

FieldTypeMeaning
generated_at string When this response was built.
month string or null The monthly release these ratings come from.
count integer How many entries are in ratings.
ratings array In the order you sent them, with repeats answered once.
ratings[].input string The entry you sent, verbatim, so the answer joins back onto your list.
ratings[].domain string or null The apex domain that entry named, or null if it named none.
ratings[].rating integer or null 0 to 100. A domain nothing links to is 0; only an unreadable entry is null.

A domain we hold no row for is a domain nothing links to, which is a rating of 0 rather than a missing one. Only an entry we cannot read as a domain at all comes back with null for both fields.

Fetching a release

The signed URL is an ordinary GET. Send no headers and no credentials with it: an Authorization header will make object storage reject the signature.

import os, requests

API = "https://checkdomainrating.com/api/v1"
headers = {"Authorization": f"Bearer {os.environ['CDR_API_KEY']}"}

catalogue = requests.get(f"{API}/downloads", headers=headers, timeout=30).json()
latest = catalogue["downloads"][0]

with requests.get(latest["url"], stream=True, timeout=60) as response:
    response.raise_for_status()
    with open(latest["filename"], "wb") as file:
        for chunk in response.iter_content(chunk_size=1 << 20):
            file.write(chunk)

Opening the files covers what to do with one once you have it. They are large, up to about 25 GB, so stream them to disk rather than into memory, and compare etag and size_bytes against a copy you already hold before downloading it again. A release you have already fetched never changes unless we republish that month, in which case its etag changes with it.

Versioning

The path carries the version. Inside v1 we will add fields but never remove or repurpose one, so parse defensively and ignore what you do not recognise. A breaking change would arrive as v2, with v1 kept running.