getdr: bulk Ahrefs DR checker
01 What it does
getdr checks Ahrefs Domain Rating (DR) for one domain, a list, a file or stdin. It is one static binary for Linux, macOS and Windows, open source under the MIT license. DR comes from the official free Ahrefs endpoint: checks cost nothing and use no API units, you only need a free Ahrefs API key.
█ █ ▄▀▀▄ domain saas.com
█ █ █ █ rating 49 ▐█████████▌░░░░░░░░░░ /100
▀▀▀█ ▀▀█ trend ▁▅█ +4 since Sep 17
█ █ checked 2026-10-28 · 12 min ago, cached
█ ▄▄▀ version dr3
Domain Rating by Ahrefs · getdr by Index PagesEach check is saved to a local SQLite cache with its date. A repeat lookup costs no request, and the card shows how DR has moved since the last check. Run getdr serve and the same binary becomes a small HTTP API: one machine keeps the key and the cache, and every script, laptop and agent on your network asks it.
getdr comes in three parts, and all of them return the same JSON record:
- getdr – the command-line tool and the
getdr serveAPI. - getdr-mcp – an MCP server with one tool,
get_dr, for Claude, Cursor and other MCP clients. - getdr-skill – an agent skill. Its bash script works even where the binary is not installed.
02 Install
With Go 1.24 or newer:
go install -trimpath github.com/index-pages/getdr@latestThe binary lands in $(go env GOPATH)/bin; make sure that directory is on your PATH.
Without Go, take an archive from GitHub Releases. Every release has Linux, macOS and Windows builds for amd64 and arm64, plus checksums.txt:
tar -xzf getdr_0.1.0_linux_amd64.tar.gz
sha256sum -c --ignore-missing checksums.txt
sudo install getdr /usr/local/bin/
getdr -VThe binary is static: no cgo, SQLite is compiled in, nothing else to install. To build it yourself:
git clone https://github.com/index-pages/getdr
cd getdr
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o getdr .For getdr serve there is a Dockerfile. The image is built FROM scratch and holds only the binary and a CA bundle. It runs as an unprivileged user and keeps the cache in /data:
docker build -t getdr .
docker run -d -p 8080:8080 -e GETDR_KEY=your-key -v getdr-data:/data getdrHomebrew, npm and PyPI packages are planned, but not published yet.
03 Free Ahrefs key
Requests to the Ahrefs Domain Rating endpoint are free and use no API units. Each request still has to carry an API key, and the key is free as well:
- Sign up for a free account at ahrefs.com, unless you already have one.
- Open Account settings → API keys.
- Create a key and copy it.
getdr looks for the key in four places and takes the first one it finds:
| Where | Example |
|---|---|
| flag | getdr --key KEY saas.com |
GETDR_KEY | export GETDR_KEY=KEY |
AHREFS_KEY | export AHREFS_KEY=KEY |
| config file | key = "KEY" in ~/.config/getdr/config.toml |
Check that it works with getdr key check. getdr config show tells you which source the key came from and prints only its last 4 characters. A key is needed only when something has to be fetched: values that are still fresh in the cache are shown without one.
Exit code 3 always means a key problem. With "no Ahrefs API key", none of the sources above has one. With "rejected by Ahrefs (HTTP 401)", the key is wrong or revoked, so create a new one. With --api, the getdr server has no key or Ahrefs rejected it, and the fix belongs on the server.
Ahrefs publishes no rate limit for this endpoint. In practice about 40–60 requests a minute go through. getdr packs up to 1000 targets into one request, sends no more than rpm requests a minute (40 by default) and retries 429 and 5xx answers with exponential backoff.
To use one key across several machines, run getdr serve on one of them and point the rest at it with api (see HTTP API). The clients then need no key at all.
04 Usage
| Command | What you get |
|---|---|
getdr saas.com | The card, on a terminal. |
getdr https://www.example.com/page | A URL is reduced to its host. |
getdr a.com b.com c.com | Several targets, sorted by DR. |
getdr -f domains.txt | One target per line; # comments are allowed. |
cat domains.txt | getdr > dr.tsv | In a pipe: domain and DR, tab-separated. |
getdr -f domains.txt --format json | NDJSON, one object per line. |
getdr -b a.com b.com | Only the values. |
getdr -f domains.txt --min 30 -q | Keeps DR 30 and above; exit code 1 if any target falls outside. |
getdr saas.com --version dr4 | The new DR formula. |
getdr saas.com --fresh | Skips the cache and fetches again. |
Targets are normalized before anything is sent. A URL becomes its host, www. is dropped, everything is lower-cased, punycode labels (xn--…) are decoded to Unicode, and duplicates are removed. So xn--e1afmkfd.xn--p1ai and ПРИМЕР.РФ are both checked as пример.рф. --raw sends targets exactly as written.
One bad line does not spoil the list. Input with no host, with whitespace or with control characters is rejected before sending. When Ahrefs refuses a target (localhost, a..com), getdr drops it from the batch and resends the rest. Each such target gets getdr: invalid target: X on stderr and an empty result, the run ends with exit code 2, and nothing about it is cached.
-f, --file FILE read targets from FILE ("-" for stdin)
-o, --out FILE write the result to FILE instead of stdout
--format F card, tsv, csv or json
-b, --bare only the DR values
--fields LIST domain,dr,version,checked_at,delta,cached
--sort sort by DR (card always sorts)
--all card: show all rows
--min N, --max N filter by DR; exit 1 if any target fails
--raw no target normalization
--version V dr3 (default) or dr4
--fresh refetch and update the cache
--no-cache neither read nor write the cache
--ttl DUR cache TTL for this run (7d, 12h, 30m)
--api URL use a getdr server instead of Ahrefs
--key KEY Ahrefs API key
--config PATH config file
--listen ADDR serve: listen address
--token T serve: required token; with --api: token to send
-q, --quiet no progress, attribution or warnings on stderr
-v, --verbose cache hits, API calls and batches on stderr
-V print the getdr versionCommands: getdr serve, getdr cache stats | clear | purge, getdr config show, getdr key check.
| Exit code | Meaning |
|---|---|
| 0 | Everything checked. |
| 1 | A target is outside --min / --max. |
| 2 | An error, invalid targets, or targets that got no response (timeout, rate limit). |
| 3 | The Ahrefs API key is missing or rejected. |
05 Output
On a terminal getdr prints the card. When stdout is a pipe or a file, it switches to tsv. --format and -b override both.
| Format | What you get |
|---|---|
card | A big number, a bar, the trend and a summary, for people. The color follows DR: 0–19 gray, 20–39 yellow, 40–69 green, 70+ blue. |
tsv | domain<TAB>dr, no header; DR is empty when there is no data. |
csv | The same columns with a header row. |
json | NDJSON, one record per target. |
-b | DR values only, one per line, an empty line for no data. |
--fields picks the tsv and csv columns from domain,dr,version,checked_at,delta,cached; the default is domain,dr. A json record is always complete, and delta is added when you list it. The JSON record is the same in the CLI, the server, the MCP tool and the skill:
{"target":"saas.com","dr":49,"version":"dr3","checked_at":"2026-10-28T12:00:00Z","cached":true}| Field | Meaning |
|---|---|
target | The normalized target, or the target as written with --raw. |
dr | Domain Rating, 0–100. null when the target was invalid, Ahrefs did not respond, or (rarely) Ahrefs has no data. |
version | dr3 or dr4. The two formulas give values that cannot be compared. |
checked_at | When the value was measured, RFC 3339 UTC. null for an invalid target or no response. |
cached | true when the value came from the cache. |
Ahrefs answers 0 for domains it does not know, so 0 is a normal value. A null DR with a null date means the target was invalid or got no response this time. A null DR with a date means Ahrefs said explicitly that it has no data.
The card respects NO_COLOR. On TERM=dumb or a locale without UTF-8 it falls back to plain ASCII, without big digits and bars. A list longer than 200 rows shows the top 200; --all shows every row.
06 Configuration
Each setting is taken from the first place that has it: a command-line flag, then a GETDR_* environment variable, then the config file, then the built-in default. The key is also read from AHREFS_KEY, which ranks below GETDR_KEY. An empty environment variable counts as unset.
getdr uses the first config file it finds: --config PATH, then $GETDR_CONFIG, then $XDG_CONFIG_HOME/getdr/config.toml (~/.config/getdr/config.toml when XDG_CONFIG_HOME is unset), then /etc/getdr/config.toml. A file named with --config or GETDR_CONFIG must exist. An unknown setting is an error, so a typo cannot slip by. ~/ at the start of a path is expanded.
key = ""
api = ""
version = "dr3"
[cache]
path = "~/.cache/getdr/cache.db"
ttl = "7d"
ttl_none = "1d"
max_age = "30d"
[ahrefs]
batch = 1000
rpm = 40
timeout = "30s"
retries = 3
[serve]
listen = "127.0.0.1:8080"
token = ""
max_targets = 10000| Setting | Env | Flag | Default | Meaning |
|---|---|---|---|---|
key | GETDR_KEY, AHREFS_KEY | --key | – | Ahrefs API key. |
api | GETDR_API | --api | – | URL of a getdr server. When set, the CLI asks it instead of Ahrefs. |
version | GETDR_VERSION | --version | dr3 | DR version, dr3 or dr4. |
cache.path | GETDR_CACHE_PATH | – | ~/.cache/getdr/cache.db | SQLite cache file; $XDG_CACHE_HOME/getdr/ when that is set. |
cache.ttl | GETDR_CACHE_TTL | --ttl | 7d | How long a value stays fresh. |
cache.ttl_none | GETDR_CACHE_TTL_NONE | – | 1d | How long "no data" stays fresh. |
cache.max_age | GETDR_CACHE_MAX_AGE | – | 30d | Trend window; cache purge drops older checks. |
ahrefs.batch | GETDR_AHREFS_BATCH | – | 1000 | Targets per Ahrefs request, 1–1000. |
ahrefs.rpm | GETDR_AHREFS_RPM | – | 40 | Ahrefs requests per minute. |
ahrefs.timeout | GETDR_AHREFS_TIMEOUT | – | 30s | Timeout of one Ahrefs request. |
ahrefs.retries | GETDR_AHREFS_RETRIES | – | 3 | Retries on 429, 5xx and network errors. |
serve.listen | GETDR_SERVE_LISTEN | --listen | 127.0.0.1:8080 | Address getdr serve listens on. |
serve.token | GETDR_SERVE_TOKEN | --token | – | Bearer token the server requires. The CLI sends it when api is set. |
serve.max_targets | GETDR_SERVE_MAX_TARGETS | – | 10000 | Targets per POST /dr. |
Durations take days with a d suffix (7d, 1.5d) and Go durations (36h, 90m, 30s). getdr config show prints every merged value and where it came from. Secrets (key, serve.token) show only their last 4 characters.
Other environment variables getdr reads:
NO_COLORturns off colors in the card.TERM=dumb, or a locale without UTF-8 inLC_ALL,LC_CTYPEorLANG, gives a plain ASCII card.COLUMNSsets the card width when the terminal does not report one.HOME,XDG_CONFIG_HOMEandXDG_CACHE_HOMEset the default file locations.HTTP_PROXY,HTTPS_PROXYandNO_PROXYare honoured. If you go through a proxy and use a getdr server on your LAN asapi, add that server toNO_PROXY.
07 Cache and history
Every check is stored as its own row (domain, version, DR, date). That history is what the card draws the trend from and what delta is computed from. A value counts as fresh while the latest check is younger than ttl, or ttl_none for "no data"; after that getdr fetches it again. dr3 and dr4 values are kept apart. Targets that got no response are not cached.
getdr cache purge deletes checks older than max_age and always keeps the latest one per domain. getdr cache stats shows the file's path and size, how many checks and domains it holds, and the oldest and newest check. getdr cache clear deletes every check. The CLI and the server can work with the same cache file at the same time.
08 HTTP API
getdr serve is a small HTTP API in front of Ahrefs with a shared cache. Start it once with an Ahrefs key, then point CLIs, scripts and agents at it. They need no key of their own, and repeated checks come from the cache.
GETDR_KEY=your-key getdr serve --listen 0.0.0.0:8080 --token s3cret
curl -H 'Authorization: Bearer s3cret' 'http://lan-host:8080/dr?target=saas.com'Every response carries the header Server: getdr/<version> (index-pages.com). Errors are JSON: {"error":"…"}. When a token is set, /dr and /stats require Authorization: Bearer <token> and answer 401 without it. /, /healthz and /teapot are always open.
| Method | Path | What it does |
|---|---|---|
| GET | /dr?target=x.com | One result record. Optional version=dr3, fresh=1 (skip the cache), raw=1 (no normalization). |
| POST | /dr | Many targets in one request, answered as {"results":[…]}. |
| GET | /healthz | 200 ok, plain text. |
| GET | /stats | Cache size, hit rate, Ahrefs requests in the last hour. |
| GET | / | Name, version and links to these docs. |
POST /dr accepts exactly four fields:
{"targets":["saas.com","https://www.example.com/page"],"version":"dr3","fresh":false,"raw":false}{"results":[
{"target":"saas.com","dr":49,"version":"dr3","checked_at":"2026-10-28T12:00:00Z","cached":true},
{"target":"example.com","dr":93,"version":"dr3","checked_at":"2026-10-28T12:03:11Z","cached":false}
]}Targets are normalized and de-duplicated, and the results follow their order. The server splits the work into Ahrefs batches of up to 1000 and keeps to the rate limit. A client can ask for fresh, but it cannot change the server's TTL. Invalid targets never fail the request: they come back inside a normal 200 with dr and checked_at set to null.
| HTTP | When |
|---|---|
| 400 | Missing target, invalid version, invalid JSON, an unknown field or data after the JSON object, no valid targets. |
| 401 | A token is required and missing or wrong. |
| 404 | Unknown path. |
| 405 | Wrong method, including HEAD /dr, which never triggers a lookup. |
| 413 | More than max_targets targets (10000 by default). |
| 503 | The server has no Ahrefs key, or Ahrefs rejected it. |
/stats answers like this; lookups counts targets since the server started, rows are stored checks and domains are distinct domains:
{
"cache": {"size_bytes":45328,"rows":1200,"domains":950,
"versions":{"dr3":900,"dr4":50},"oldest":"2026-10-01T09:00:00Z","newest":"2026-10-28T12:03:11Z"},
"lookups": {"hits":1830,"misses":120,"hit_rate":0.9385},
"ahrefs_requests_last_hour": 3
}When several clients ask for the same domain and version at once, the server makes one Ahrefs request for all of them. The CLI talks to a server like this:
getdr --api http://lan-host:8080 --token s3cret -f domains.txtThe same works with api and serve.token in the config file, or with GETDR_API and GETDR_SERVE_TOKEN. The CLI sends one POST /dr for the targets its own cache does not cover and passes --fresh on. Proxy variables apply to this request too, so add a LAN server to NO_PROXY.
09 AI agents
getdr-mcp is an MCP server with one tool, get_dr. It makes no Ahrefs requests of its own: it asks a getdr server or the getdr binary. It runs over stdio, has no runtime dependencies and needs Node.js 18 or later. Add it to Claude Desktop, Claude Code, Cursor or another MCP client:
{
"mcpServers": {
"getdr": {
"command": "npx",
"args": ["-y", "getdr-mcp"]
}
}
}This uses the getdr binary on your PATH, with its own config, cache and key. To use a getdr server on your network instead, add "env": {"GETDR_API": "http://lan-host:8080", "GETDR_SERVE_TOKEN": "s3cret"}. The server checks GETDR_API first, then the binary; with neither, the tool returns an error with setup instructions.
| Argument | Type | Meaning |
|---|---|---|
targets | string[] | Required, non-empty. Domains or URLs. |
version | "dr3" | "dr4" | DR formula, dr3 by default. It is always sent explicitly, so a server or config set to dr4 does not change it. |
fresh | boolean | Ignore the cache and fetch again. |
The tool returns a JSON array of records followed by the attribution line. Clients on MCP protocol 2025-06-18 or later also get structuredContent with an outputSchema. If some targets got no response, the result still comes back with every record and a note naming those targets.
getdr-skill is a folder with a SKILL.md and a bash script, for any agent that loads Agent Skills. For Claude Code:
git clone https://github.com/index-pages/getdr-skill ~/.claude/skills/getdrThe script scripts/dr.sh takes the first backend available: a getdr server in GETDR_API, then the getdr binary on PATH, then the free Ahrefs endpoint directly with GETDR_KEY or AHREFS_KEY. With none of them it exits with code 3 and links to getting a free key. It needs bash, curl and jq 1.6 or newer; with the binary installed, bash alone is enough. The output is the same NDJSON on every path.
10 Attribution and privacy
Domain Rating data comes from Ahrefs and is subject to the Domain Rating License, which requires the attribution "Domain Rating by Ahrefs". getdr prints it in the card footer, and once per run on stderr for machine formats, never on stdout (-q silences it). Keep it when you publish the numbers. getdr is not affiliated with Ahrefs.
getdr connects to two places only: api.ahrefs.com and the getdr server you set in api. It has no telemetry, no update checks and no analytics, and it refuses redirects to any other host. Every request carries the User-Agent:
getdr/<version> (+https://index-pages.com/getdr)
You can check this in the source: all outgoing requests are made in internal/ahrefs and internal/remote. getdr-mcp connects only to the server in GETDR_API, and otherwise starts the binary. The skill's script talks to GETDR_API or api.ahrefs.com and passes keys to curl in a temporary file with mode 600, never on the command line.
11 Who makes it
getdr is made by Index Pages. Our main product is a URL indexing service: it pushes pages into Google, Bing, Yandex and 10+ other engines, the indexes ChatGPT, Claude, Perplexity and AI Overviews answer from. A DR check tells you which domains are worth a link. Index Pages gets the pages with those links indexed.
The service is on the home page, and its REST API for scripts and agents is on the API page. Bugs and ideas for getdr go to GitHub issues.