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.
/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
| Name | Type | Meaning |
|---|---|---|
| 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
| Field | Type | Meaning |
|---|---|---|
| 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.
| Status | Code | When |
|---|---|---|
| 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.
/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
| Field | Type | Meaning |
|---|---|---|
| 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.