free tool · getdr

getdr: bulk Ahrefs DR checker

Version 0.1.0License MITRuns on Linux · macOS · WindowsPrice free

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 Pages

Each 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 serve API.
  • 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@latest

The 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 -V

The 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 getdr

Homebrew, 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:

getdr looks for the key in four places and takes the first one it finds:

WhereExample
flaggetdr --key KEY saas.com
GETDR_KEYexport GETDR_KEY=KEY
AHREFS_KEYexport AHREFS_KEY=KEY
config filekey = "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

CommandWhat you get
getdr saas.comThe card, on a terminal.
getdr https://www.example.com/pageA URL is reduced to its host.
getdr a.com b.com c.comSeveral targets, sorted by DR.
getdr -f domains.txtOne target per line; # comments are allowed.
cat domains.txt | getdr > dr.tsvIn a pipe: domain and DR, tab-separated.
getdr -f domains.txt --format jsonNDJSON, one object per line.
getdr -b a.com b.comOnly the values.
getdr -f domains.txt --min 30 -qKeeps DR 30 and above; exit code 1 if any target falls outside.
getdr saas.com --version dr4The new DR formula.
getdr saas.com --freshSkips 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 version

Commands: getdr serve, getdr cache stats | clear | purge, getdr config show, getdr key check.

Exit codeMeaning
0Everything checked.
1A target is outside --min / --max.
2An error, invalid targets, or targets that got no response (timeout, rate limit).
3The 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.

FormatWhat you get
cardA 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.
tsvdomain<TAB>dr, no header; DR is empty when there is no data.
csvThe same columns with a header row.
jsonNDJSON, one record per target.
-bDR 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}
FieldMeaning
targetThe normalized target, or the target as written with --raw.
drDomain Rating, 0–100. null when the target was invalid, Ahrefs did not respond, or (rarely) Ahrefs has no data.
versiondr3 or dr4. The two formulas give values that cannot be compared.
checked_atWhen the value was measured, RFC 3339 UTC. null for an invalid target or no response.
cachedtrue 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
SettingEnvFlagDefaultMeaning
keyGETDR_KEY, AHREFS_KEY--key–Ahrefs API key.
apiGETDR_API--api–URL of a getdr server. When set, the CLI asks it instead of Ahrefs.
versionGETDR_VERSION--versiondr3DR version, dr3 or dr4.
cache.pathGETDR_CACHE_PATH–~/.cache/getdr/cache.dbSQLite cache file; $XDG_CACHE_HOME/getdr/ when that is set.
cache.ttlGETDR_CACHE_TTL--ttl7dHow long a value stays fresh.
cache.ttl_noneGETDR_CACHE_TTL_NONE–1dHow long "no data" stays fresh.
cache.max_ageGETDR_CACHE_MAX_AGE–30dTrend window; cache purge drops older checks.
ahrefs.batchGETDR_AHREFS_BATCH–1000Targets per Ahrefs request, 1–1000.
ahrefs.rpmGETDR_AHREFS_RPM–40Ahrefs requests per minute.
ahrefs.timeoutGETDR_AHREFS_TIMEOUT–30sTimeout of one Ahrefs request.
ahrefs.retriesGETDR_AHREFS_RETRIES–3Retries on 429, 5xx and network errors.
serve.listenGETDR_SERVE_LISTEN--listen127.0.0.1:8080Address getdr serve listens on.
serve.tokenGETDR_SERVE_TOKEN--token–Bearer token the server requires. The CLI sends it when api is set.
serve.max_targetsGETDR_SERVE_MAX_TARGETS–10000Targets 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_COLOR turns off colors in the card.
  • TERM=dumb, or a locale without UTF-8 in LC_ALL, LC_CTYPE or LANG, gives a plain ASCII card.
  • COLUMNS sets the card width when the terminal does not report one.
  • HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME set the default file locations.
  • HTTP_PROXY, HTTPS_PROXY and NO_PROXY are honoured. If you go through a proxy and use a getdr server on your LAN as api, add that server to NO_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.

MethodPathWhat it does
GET/dr?target=x.comOne result record. Optional version=dr3, fresh=1 (skip the cache), raw=1 (no normalization).
POST/drMany targets in one request, answered as {"results":[…]}.
GET/healthz200 ok, plain text.
GET/statsCache 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.

HTTPWhen
400Missing target, invalid version, invalid JSON, an unknown field or data after the JSON object, no valid targets.
401A token is required and missing or wrong.
404Unknown path.
405Wrong method, including HEAD /dr, which never triggers a lookup.
413More than max_targets targets (10000 by default).
503The 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.txt

The 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.

ArgumentTypeMeaning
targetsstring[]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.
freshbooleanIgnore 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/getdr

The 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:

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.