Every check in the Tools section works not only from the page but from your own program: the same computation and the same shape of answer. Everything you need to start in ten minutes is here.
How to get a key
- Sign in to the tools account with a code sent to your email.
- An access key comes with the Pro plan. The free and Basic levels have no key — an attempt to issue one answers
TOOL_KEY_NOT_ALLOWED. - In the “Access keys” section press “Create key” and give it a name — that is how you will recognise it in the list.
- The key value is shown once — at the moment of creation. We store only its fingerprint, so “show it again” is impossible for you and for us. Copy the key straight away. Lost it — revoke the old one and issue a new one.
A key looks like this: the prefix tlk_ and 64 hexadecimal characters. The prefix makes it recognisable in someone else's log — do not leave a key in page source and do not commit it to a code repository.
The Pro plan allows five live keys. A revoked key stops working the same instant.
How to pass the key
The key goes in the Authorization header with the word Bearer:
Authorization: Bearer tlk_YOUR_KEY
Other ways do not work on purpose: Authorization: Basic …, an empty Bearer with no value, or a key without the tlk_ prefix get 401 TOOL_KEY_INVALID. We will not quietly fall back to a guest — otherwise you would hit guest limits and read them as random refusals.
A key works only on the site that issued it. The same key sent to another site of the platform answers 401 TOOL_KEY_INVALID — the same code as a made-up key.
No header at all is not an error: the request goes through as a guest, with guest limits.
The first request
One check is one POST request. The address is built from the tool kind:
POST https://seo-gen.com.ua/api/public/tools/<kind>/run
A full example you can copy and run (put your own key in):
curl -X POST https://seo-gen.com.ua/api/public/tools/http-status-checker/run \
-H "Authorization: Bearer tlk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"target":"https://example.com/"}'
The request body has two fields: target — the address to check, and the optional options — the settings of that kind.
What the answer consists of
A successful answer is 200 and two fields: data with the result and limit with the remaining volume. The example below leaves out the headers section to keep the page short — a live answer has it.
{
"data": {
"kind": "http-status-checker",
"target": "https://example.com/",
"checkedAt": "2026-09-19T08:12:07.078Z",
"cached": false,
"verdict": "ok",
"sections": [
{
"id": "summary",
"rows": [
{ "status": 200, "final": "https://example.com/", "hops": 0, "time_ms": 95, "soft_404": "" }
],
"columns": ["status", "final", "hops", "time_ms", "soft_404"]
},
{ "id": "chain", "rows": [], "columns": ["step", "from", "to", "status", "scheme"] }
],
"notes": []
},
"limit": { "remaining": null }
}
data.kind— the tool kind — the same one as in the addressdata.target— the address that was checked;nullwhen the kind has nonedata.checkedAt— the time of the check, ISO-8601 with a time zonedata.cached—true— the answer came from our cache (it lives 10 minutes); the time stays the time of the actual checkdata.verdict— one word of summary — the list is belowdata.sections— result sections: each has anid,rowsandcolumns— the column orderdata.notes— codes of caveats (not ready-made text): why not everything was checked and what is worth knowinglimit.remaining— how many checks are left;nullmeans “no limit”
If a section is shown in part, it carries capped with two numbers: how many rows are shown and how many were found.
Summary words
ok— checked, no trouble foundguess— determined roughly: the answer rests on one weak cluepartial— checked in part — some of it did not happen for OUR reason (our time budget, our size limit, we were slowed down). The “checked N of M” numbers are in the summarywarn— there are caveatsunknown— could not be determined: the site's answer could not be parsedfail— a fault was found
Tool kinds and what to send them
Every kind below works with a key. The “Options” column is the content of the options field: round brackets hold the type and the default, square brackets the allowed values.
Kind (kind) | Name | Address | Options (options) | Answer sections |
|---|---|---|---|---|
audit | Website audit | required | user_agent (select, "*") [*|Googlebot|Bingbot|YandexBot|GPTBot|ClaudeBot|PerplexityBot|Google-Extended] | summary, issues, robots_issues, sitemap_issues, hreflang_issues, sources, conflicts, plan, chain, checks, directives, meta, headings |
seo-checker | Page SEO analysis | required | check_links (boolean, true) | summary, issues, indexability, meta, headings, content, images, links |
cms-detector | CMS Detector / Detect a site CMS | required | — | cms, signals, tech |
indexability-checker | Indexability check | required | user_agent (select, "*") [*|Googlebot|Bingbot|YandexBot|GPTBot|ClaudeBot|PerplexityBot|Google-Extended]; check_targets (boolean, true) | verdict, conflicts, sources, targets, plan |
http-status-checker | HTTP Status Checker | required | check_domain_variants (boolean, false) | summary, chain, variants, headers |
redirect-checker | Redirect Checker | required | check_domain_variants (boolean, false) | summary, chain, variants, headers |
robots-checker | Robots.txt Checker | required | user_agent (select, "*") [*|Googlebot|Bingbot|YandexBot|GPTBot|ClaudeBot|PerplexityBot|Google-Extended]; paths (list); custom_file (textarea) | verdict, paths, ai_bots, issues, file, health |
sitemap-checker | Sitemap Checker | required | check_urls (boolean, true); sample_size (number, 20) | summary, robots, issues, tree, sample, urls, searched |
robots-meta-checker | Robots Meta Checker | required | user_agent (select, "*") [*|Googlebot|Bingbot|YandexBot|GPTBot|ClaudeBot|PerplexityBot|Google-Extended]; with_robots_txt (boolean, true) | verdict, issues, sources, directives |
canonical-checker | Canonical Checker | required | check_target (boolean, true) | summary, checks, chain |
link-extractor | Link Extractor | required | check_status (boolean, true); unique_only (boolean, false) | summary, links, domains, special |
link-attributes | Nofollow / Sponsored / UGC Checker | required | my_domain (domain); check_status (boolean, true) | page_block, summary, links, mine |
anchor-analysis | Link anchor analysis | required | commercial_words (textarea) | types, anchors, words, conflicts |
meta-extractor | Meta tag extraction | required | — | summary, tags, cards, code |
hreflang-checker | Hreflang Checker | required | check_return (boolean, true) | summary, alternates, reciprocal, issues |
headings-extractor | H1-H6 Extractor | not needed | html (textarea) | tiles, outline |
pagespeed-checker | PageSpeed / Core Web Vitals Checker | required | strategy (select, "mobile") [mobile|desktop] | summary, vitals, field, opportunities |
broken-links | Broken Link Checker | required | mode (select, "page") [page|site]; max_pages (number, 100); check_status (boolean, true); urls (textarea) | summary, broken, redirects, skipped |
internal-links | Internal links checker | required | mode (select, "page") [page|site]; max_pages (number, 100); check_status (boolean, true); subdomains_internal (boolean, false); urls (textarea) | summary, links, dead_ends, orphans |
external-links | External links checker | required | mode (select, "page") [page|site]; max_pages (number, 100); check_status (boolean, true); subdomains_internal (boolean, false); urls (textarea) | summary, links, skipped |
sitemap-generator | sitemap.xml generator | not needed | urls (textarea); alternates (boolean, true); locales (text, "uk, ru, en"); lastmod (date); changefreq (select) [|always|hourly|daily|weekly|monthly|yearly|never]; priority (number) | numbers, xml, notes, next |
json-ld-validator | JSON-LD Validator | not needed | code (textarea) | summary, blocks, issues |
hreflang-generator | hreflang generator | not needed | versions (rows); x_default (select); format (select, "html") [html|xml|http]; site_country (country, "UA"); comments (boolean); heads (textarea) | numbers, code, checks, map |
Address lists and files
A list of addresses and a site crawl go into a queue: the request answers at once, and the result is picked up by a link.
curl -X POST https://seo-gen.com.ua/api/public/tools/http-status-checker/batch \
-H "Authorization: Bearer tlk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"list":"https://example.com/\nhttps://example.com/nope\nне адрес"}'
The 202 answer carries the job id, a link to it, how many addresses were accepted and the list of rejected lines right away — with the line number and the reason.
{
"data": {
"id": "537f4024-0537-4abc-b564-56dc55569538",
"url": "/api/public/tools/jobs/537f4024-0537-4abc-b564-56dc55569538",
"accepted": 2,
"rejected": [ { "code": "TARGET_INVALID", "line": 3, "value": "не адрес" } ],
"status": "queued"
},
"limit": { "remaining": 4997 }
}
A list can also be sent as a file — multipart/form-data, field file; CSV, XLSX and TXT up to 5 MB:
curl -X POST https://seo-gen.com.ua/api/public/tools/http-status-checker/batch \
-H "Authorization: Bearer tlk_YOUR_KEY" \
-F "file=@spisok.csv"
Progress and result come from the same link. job.status goes queued → running → done, and job.done with job.total show the progress:
curl https://seo-gen.com.ua/api/public/tools/jobs/537f4024-0537-4abc-b564-56dc55569538 \
-H "Authorization: Bearer tlk_YOUR_KEY"
A finished result is exported as a file — a table or JSON:
curl -OJ https://seo-gen.com.ua/api/public/tools/jobs/537f4024-0537-4abc-b564-56dc55569538/export.csv \
-H "Authorization: Bearer tlk_YOUR_KEY"
curl -OJ https://seo-gen.com.ua/api/public/tools/jobs/537f4024-0537-4abc-b564-56dc55569538/export.json \
-H "Authorization: Bearer tlk_YOUR_KEY"
A site crawl uses the same route, but instead of a list it takes one address and options.mode = "site". How many pages to walk is set by the plan.
What counts as spending
- One check
POST …/runis one unit of the plan's monthly volume and one key request. - A list of N addresses is N units: every address is a separate trip to someone else's site.
- A site crawl is one unit at the moment it enters the queue; how many pages it walks is capped by the plan.
- An answer from the cache (
cached: true) still spends volume: the volume counts requests, not trips. - A refusal by limit (
QUOTA_EXCEEDED,API_QUOTA_EXCEEDED) arrives BEFORE the work: we will not crawl someone else's site and only then say “not allowed”. - Looking at job progress and exporting the result spend nothing.
Refusals
A refusal is always a body with two fields: code as a machine word and error as a sentence in the language of the request (taken from the Accept-Language header). The same code is repeated in the X-CMS-Error-Code header.
| Code | HTTP | What it means |
|---|---|---|
TOOL_KEY_INVALID | 401 | The key did not match: wrong, revoked, from another site, or passed the wrong way. |
TOOL_KEY_NOT_ALLOWED | 403 | The key exists, but the plan no longer allows it — the subscription was downgraded or ended. |
TOOL_KIND_UNKNOWN | 400 | No such tool kind. |
TOOL_NOT_IMPLEMENTED | 501 | The kind is declared but still in development. |
TARGET_INVALID | 422 | No address was passed, or it does not look like an address. |
TARGET_NOT_ALLOWED | 400 | The address does not lead to the open internet: an internal or service network. We do not follow those. |
TARGET_UNREACHABLE | 502 | The domain does not exist or does not answer. This is an answer ABOUT THE OTHER SITE, not our fault. |
TARGET_TIMEOUT | 504 | The site did not answer within 10 seconds. |
TARGET_TOO_BIG | 413 | The page is larger than 5 MB — we do not read further. |
TARGET_TOO_MANY_REDIRECTS | 502 | The redirect chain is longer than five steps. |
OUR_BUDGET | 504 | OUR time budget for the whole run ran out. This is not a fault of the site. |
QUOTA_EXCEEDED | 429 | The plan's monthly volume is used up. The count starts over next month. |
API_QUOTA_EXCEEDED | 429 | The monthly volume of KEY requests is used up — it is smaller than the check volume. |
RATE_LIMIT_EXCEEDED | 429 | The guest hourly limit. A key on a paid plan has none — the monthly volume counts there. |
BATCH_TOO_LARGE | 422 | The list holds more addresses than the plan allows. No volume was spent on it. |
BATCH_BUSY | 429 | Two jobs of this site are already running — wait for one of them to finish. |
FILE_UNREADABLE | 422 | The file could not be read. CSV, XLSX and TXT work. |
JOB_NOT_FOUND | 404 | No such job: its term (seven days) expired or it belongs to another site. |
Limits and politeness to other people's sites
Pro plan limits: 30,000 checks a month, 5,000 key requests a month, 5,000 addresses in one list, a crawl of up to 50,000 pages, five keys, 365 days of history. What is left is in the limit.remaining field of every answer.
Politeness limits towards SOMEONE ELSE'S site are lifted by no plan — they are not about who pays, they are about who is being checked:
- no more than two simultaneous requests to one host and a 500 ms pause between them;
- we wait 10 seconds for a page, read the first 5 MB, follow no more than five redirects;
- a single check has 18 seconds in total: if we do not finish, the answer comes back honestly partial (
partial), not cut off; - we introduce ourselves as
SeoGenCMS-Tools— you can block us by that name in your ownrobots.txt; - two jobs per site run at the same time.
Check other people's sites with the same care you would want for your own.