Recipe
Sync link scores from a sheet or n8n
Most tool directories live in a spreadsheet, an Airtable base or a small database. This recipe turns a tool backlink check into a column fill: one request per 2,000 rows.
SEO Tools AI research team ·
The recipe in five moves
Export the domains
Take the website column of your listings, strip the protocol and paths, and remove duplicates. Duplicates cost nothing extra but make the mapping step messy.
Chunk into groups of 2,000
bulk_dr accepts up to 2,000 domains per call. A 5,000-row sheet therefore needs three calls.
Send, check ok
Call the API, read the JSON, and stop if ok is false. Log _credits so you see the balance after every run.
Write back with a date
Store the rating and today's date in two columns. Never overwrite a good value with an empty one.
Schedule once a month
Run it on the first of the month. The index moves about monthly, so more often gains nothing.
Before you automate anything
Run the check by hand on five listings first. Use the free overview action for each, which needs no key, and compare what you see with what you expected.
This catches the common surprises early: a listing that points to a subdomain, a product that lives on a path of a larger site, a domain that redirected last spring. A rating belongs to a domain, not to a product.
Decide how your sheet marks those cases, for example with a note column that reads shares domain. Readers forgive a footnote far more readily than a score that looks wrong.
When the five look sane, move on to the batch. Your field reference lists the columns, and the cost example shows what a run costs.
A small Python version
Needs the requests package. It is a sketch for the shape of the job: print one real response first, then map the fields you need into your own columns.
import csv, requests
KEY = "sk_live_..."
BASE = "https://seotoolsai.org/api.php"
rows = list(csv.DictReader(open("listings.csv")))
domains = sorted({r["domain"] for r in rows})
for i in range(0, len(domains), 2000):
chunk = domains[i:i + 2000]
r = requests.post(BASE, params={"action": "bulk_dr", "key": KEY},
data={"domains": ",".join(chunk)}, timeout=60)
if r.status_code in (401, 402):
raise SystemExit(f"stop: HTTP {r.status_code}")
data = r.json()
if not data.get("ok"):
raise SystemExit("api said not ok")
print(data["_credits"]) # cost and remaining balance
open(f"raw_{i}.json", "w").write(r.text)Doing it in Google Sheets or n8n
Sheets can call a URL from Apps Script with UrlFetchApp, and n8n has an HTTP Request node. Both do the same thing as the script above.
In n8n, put a schedule trigger first, then a node that reads your sheet or table, then an item-splitting step that builds groups of 2,000, then the HTTP Request. Add a final node that writes the rating and date back.
Keep the key in the platform's credential store, not in the workflow text. Workflows get shared, copied and pasted into forums, and a key pasted with them is spent money.
What the errors mean
| Signal | Meaning | What the job does |
|---|---|---|
| HTTP 401 | Missing or bad key | Stop, alert a human |
| HTTP 402 | Credit has run out | Stop, keep old scores, top up |
ok is false | The call did not succeed | Log the body, retry next run |
| Empty result for a domain | Not in the index | Leave the cell blank |
Sync questions
How do I avoid paying twice if the job crashes halfway?
Write the date next to each score as you go, and have the next run skip rows already stamped this month. Chunks of 2,000 mean a crash costs at most one credit, so the risk is tiny, but the habit keeps the sheet tidy.
Can a non-programmer set this up?
Mostly yes. An HTTP Request node in n8n or Make needs only a URL, a key and a list. The fiddly part is turning the response into columns, so do one test call and look at the JSON before you build the mapping.