Data API for 222,825,218 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.

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.

Authentication

Generate a key on your account page and send it as a bearer token:

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 endpoint additionally requires 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 the subscription behind it is not active.
422 too_many_domains More than 100 domains were sent in one request.
429 rate_limited Too many requests. Retry-After says how long to wait.

Rate limits

60 requests a minute and 5,000 a day per key, 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.

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.