Semrush Gateway API v1

Semrush Gateway API

One unified API for Semrush data — backlinks, referring domains, ranked keywords, keyword research, and bulk analysis — behind a single key. Requests are load-balanced across a pool of independent data sources with automatic failover, so you get one stable endpoint instead of juggling many.

BASE URL https://api.seo1.us AUTH X-API-Key FORMAT JSON / CSV

Overview

Every endpoint lives under https://api.seo1.us/v1/ and speaks JSON (a few also return CSV). Send your API key in the X-API-Key header on every request. That's the whole contract — pick an endpoint, POST a JSON body, read the result.

  • Automatic failover. If one data source is busy or down, the gateway transparently retries the next one. You never see the plumbing.
  • One key, all endpoints. Backlinks, keywords, and rankings all share the same key and base URL.
  • CORS-enabled. /v1/* accepts cross-origin browser requests (no credentials), so you can call it directly from a frontend.
  • No Semrush login. You never touch Semrush accounts or sessions — just the gateway.
🔑 Need a key? Contact the gateway administrator. Each key is issued with its own rate limit and daily quota, and can be revoked at any time.

Authentication

Pass your key in the X-API-Key header on every /v1/* request. Keys are prefixed with smg_. A missing or invalid key returns 401.

bash
curl https://api.seo1.us/v1/me/usage \
  -H "X-API-Key: smg_your_api_key"

The public /health endpoint is the only one that needs no key.

Quick start

Pull the organic keywords a domain ranks for, in three languages:

cURL
curl -X POST https://api.seo1.us/v1/ranked-keywords \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com", "database": "us", "page_size": 50}'
Python
import requests

BASE = "https://api.seo1.us"
KEY  = "smg_your_api_key"

r = requests.post(
    f"{BASE}/v1/ranked-keywords",
    headers={"X-API-Key": KEY},
    json={"domain": "example.com", "database": "us", "page_size": 50},
    timeout=60,
)
r.raise_for_status()
print(r.json())
Node.js
const res = await fetch("https://api.seo1.us/v1/ranked-keywords", {
  method: "POST",
  headers: {
    "X-API-Key": "smg_your_api_key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ domain: "example.com", database: "us", page_size: 50 }),
});
const data = await res.json();
console.log(data);

Rate limits & quotas

Each key has a per-minute rate limit (sliding 60-second window) and a daily quota (resets at 00:00 UTC). Both are set when the key is issued.

429Over the per-minute limit → {"error": "Rate limit exceeded"} with a Retry-After header (seconds to wait).
429Over the daily quota → {"error": "Daily quota exceeded"}. Resets at UTC midnight.

Every /v1/* call counts toward your quota whether it succeeds or fails — except /v1/me/usage, which is free. Check your current usage any time:

bash
curl https://api.seo1.us/v1/me/usage \
  -H "X-API-Key: smg_your_api_key"

Response headers

Every /v1/* response carries gateway metadata headers:

X-Request-IdUnique ID for this request. Include it when contacting support about a specific call.
X-ProviderOpaque identifier of the data source that served the request. Useful for support; not something you need to act on.
X-Failover-CountHow many data sources were tried before one succeeded. 0 = served on the first try.
X-Total-AvailableOn large/CSV responses: total rows available upstream for the query.
X-Rows-ReturnedOn large/CSV responses: rows actually returned in this response.
Retry-AfterOn 429: seconds to wait before retrying.

Errors

Errors return a JSON body of the shape {"error": "..."} with a matching HTTP status.

StatusMeaningWhat to do
401Missing or invalid API keyCheck the X-API-Key header.
422Validation error in the request body (missing field, >200 targets, bad value)Fix the body. The response names the offending field.
429Rate limit or daily quota exceededBack off; honor Retry-After.
503Every capable data source failed or was unavailableRetry shortly. Body includes attempts[] and request_id.

A 503 means the gateway already tried every data source that supports the endpoint and none answered — it is rare and usually transient.

json
{
  "error": "All providers failed or unavailable",
  "attempts": [
    {"provider": "a", "status": 503, "error": "upstream busy"},
    {"provider": "b", "status": 502, "error": "bad gateway"}
  ],
  "request_id": "1717df10717f43f2b37703b16e8cb6b7"
}

Bulk analysis

Backlink-overview counters for up to 200 domains or URLs in a single call — authority score, total backlinks, referring domains, follow/nofollow split, and outbound counts. This is the most efficient endpoint for profiling many targets at once; a 200-target batch returns in roughly 10–20 seconds.

POST /v1/bulk-analysis

Returns one result row per requested target, in request order.

Body parameters

FieldTypeDefaultNotes
targetsrequiredarray1–200 items, each { target, target_type }.
targets[].targetrequiredstringDomain or full URL.
targets[].target_typestringroot_domainroot_domain | domain | url
concurrencyint81–20 (compatibility; the batch is one upstream call).
cURL
curl -X POST https://api.seo1.us/v1/bulk-analysis \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": [
      {"target": "amazon.com",  "target_type": "root_domain"},
      {"target": "apple.com",   "target_type": "root_domain"},
      {"target": "shopify.com", "target_type": "root_domain"}
    ]
  }'
json — response
{
  "success": true,
  "count": 1,
  "results": [
    {
      "target": "apple.com",
      "target_type": "root_domain",
      "authority_score": 100,
      "total_backlinks": 6162246181,
      "referring_domains": 5761755,
      "follow": 4879470067,
      "nofollow": 1262596675,
      "text": 3206326108,
      "image": 2136212652,
      "ips": 1318698,
      "outbound_links": 16861408,
      "outbound_domains": 905859,
      "error": null
    }
  ],
  "request_id": "5846269872b6438284f292770c458d35"
}
ℹ️ A target the upstream can't serve comes back with zeros and a non-null error string — the call as a whole still returns 200. For more than 200 targets, split into batches of 200.
GET /v1/bulk-analysis/export CSV

The same bulk-analysis data, returned as a CSV file in one request.

Query parameters

ParamDefaultNotes
targetsrequiredComma- or newline-separated, max 200.
target_typeroot_domainApplied to all targets.
concurrency81–20.
bash
curl -o bulk.csv \
  "https://api.seo1.us/v1/bulk-analysis/export?targets=amazon.com,apple.com,shopify.com" \
  -H "X-API-Key: smg_your_api_key"

Columns: target, target_type, authority_score, total_backlinks, referring_domains, follow, nofollow, text, image, ips, outbound_links, outbound_domains, error.

Referring domains

Backlinks aggregated to the domain level — one row per referring domain instead of per link.

POST /v1/referring-domains

Same body shape as /v1/backlinks; defaults to sorting by domain_ascore.

Body parameters

FieldTypeDefaultNotes
targetrequiredstringDomain or URL.
target_typestringroot_domainroot_domain | domain | url
limitint100Rows to return.
offsetint0Pagination offset.
sort_fieldstringdomain_ascoreField to sort by.
sort_typestringdescasc | desc
cURL
curl -X POST https://api.seo1.us/v1/referring-domains \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"target": "example.com", "target_type": "root_domain", "limit": 100}'

Each row includes domain, domain_ascore, backlinks_num, ip, country, first_seen, and last_seen.

GET /v1/referring-domains/export CSV

The top N referring domains (default and max 1000), ordered by domain_ascore descending. CSV by default, or JSON with format=json.

The native upstream CSV export is disabled by every data source, so the gateway assembles this itself: it paginates the working POST /v1/referring-domains endpoint (100 rows per page, offsets 0…900) through the load-balancer, then dedupes, sorts, and truncates to max_rows. You get the highest-authority referring domains first.

Query parameters

ParamDefaultNotes
targetrequiredDomain or URL.
target_typeroot_domainroot_domain | domain | url
max_rows1000Rows to return. Hard cap 1000.
sort_fielddomain_ascoreField to sort by.
sort_typedescasc | desc
formatcsvcsv | json
bash
curl -o referring_domains.csv \
  "https://api.seo1.us/v1/referring-domains/export?target=backlinko.com&target_type=root_domain" \
  -H "X-API-Key: smg_your_api_key"

CSV (default): text/csv attachment. Columns, in order: domain, domain_ascore, backlinks_num, ip, country, first_seen, last_seen.

JSON (format=json): { success, target, target_type, total_available, returned, referring_domains[], request_id }.

Response headers: X-Source: paginated, X-Provider: composite, X-Total-Available (e.g. 73370), X-Rows-Returned, and X-Partial: true only if an internal page hard-failed. Small sites stop early once a page returns fewer than 100 rows.

Indexed pages

The pages on a target that have received backlinks — useful for finding a site's most-linked URLs.

POST /v1/indexed-pages

The gateway automatically routes this to a data source that supports it.

Body parameters

FieldTypeDefaultNotes
targetrequiredstringDomain or URL.
target_typestringroot_domainroot_domain | domain | url
limitint100Rows to return.
broken_onlyboolfalseOnly return pages that 4xx/5xx.
cURL
curl -X POST https://api.seo1.us/v1/indexed-pages \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"target": "example.com", "target_type": "root_domain", "limit": 100}'

Anchors

The distribution of anchor text across a target's backlinks.

POST /v1/anchors

The gateway automatically routes this to the data source that supports anchor data.

Body parameters

FieldTypeDefaultNotes
targetrequiredstringDomain or URL.
target_typestringroot_domainroot_domain | domain | url
limitint100Rows to return.
offsetint0Pagination offset.
cURL
curl -X POST https://api.seo1.us/v1/anchors \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"target": "example.com", "target_type": "root_domain", "limit": 100}'

Outbound domains

The external domains a site links out to, ranked by authority.

POST /v1/outbound-domains

Returns top-level overview counts plus an outbound_domains[] array.

Body parameters

FieldTypeDefaultNotes
domainrequiredstringDomain to inspect.
limitint100Rows to return.
sort_fieldstringfirsttimeField to sort by.
sort_typestringdescasc | desc
cURL
curl -X POST https://api.seo1.us/v1/outbound-domains \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com", "limit": 100}'

Includes top-level authority_score, referring_domains, total_backlinks, and outbound_links.

GET /v1/outbound-domains/export CSV

The outbound domains a site links out to (default and max 1000), ordered by domain_ascore descending. CSV by default, or JSON with format=json.

The native upstream CSV export is disabled by every data source, so the gateway assembles this from the working POST /v1/outbound-domains endpoint. That POST caps at 100 rows/call and ignores pagination, but different sort orders return different 100-row slices — so for max_rows > 100 the gateway fans out across 8 sort orders concurrently and unions the distinct domains (verified: full set recovered), then dedupes, sorts by your sort_field/sort_type, and truncates to max_rows. Requests with max_rows ≤ 100 take a single-call fast path. Quota counts once.

Query parameters

ParamDefaultNotes
domainrequiredDomain to inspect (alias: target).
max_rows1000Rows to return. Hard cap 1000.
sort_fielddomain_ascoreField to sort by.
sort_typedescasc | desc
formatcsvcsv | json
bash
curl -o outbound_domains.csv \
  "https://api.seo1.us/v1/outbound-domains/export?domain=hasselblad.com" \
  -H "X-API-Key: smg_your_api_key"

CSV (default): text/csv attachment. Columns, in order: domain, domain_ascore, links_num, category, first_seen, last_seen.

JSON (format=json): { success, domain, total_available, returned, outbound_domains[], request_id }.

Response headers: X-Source (multi-sort-union or single-call), X-Provider: composite, X-Total-Available, X-Rows-Returned, and X-Partial: true if a sort slice failed.

Ranked keywords

The organic keywords a domain ranks for in a given Semrush database (country).

POST /v1/ranked-keywords

Body parameters

FieldTypeDefaultNotes
domainrequiredstringDomain to analyze.
databasestringusCountry code — see country codes.
search_typestringdomaindomain | subdomain | url | subfolder
pageint1Page number.
page_sizeint100Rows per page (max 200).
order_bystringtrafficPercentField to sort by.
order_dirstringdescasc | desc
cURL
curl -X POST https://api.seo1.us/v1/ranked-keywords \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "database": "us",
    "page_size": 100,
    "order_by": "trafficPercent",
    "order_dir": "desc"
  }'
💡 Not sure which database has data for a domain? Call /v1/top-countries first to see where it has traffic.

Keyword research

The Keyword Magic Tool — expand a seed phrase into related keywords with volume, difficulty, CPC, intent, and SERP features.

POST /v1/keyword-research

Body parameters

FieldTypeDefaultNotes
phraserequiredstringSeed keyword.
databasestringusCountry code — see country codes.
page_sizeint100Rows per page.
max_pagesint1Up to 300 (≈ 30,000 rows).
match_typestringbroadbroad | phrase | exact | related | all
questions_onlyboolfalseReturn only question keywords.
filtersobjectSee below.

filters object

All optional: volume_min/volume_max, kd_min/kd_max, cpc_min/cpc_max, word_count_min/word_count_max, include_phrases[], exclude_phrases[], intent[], serp_features[].

cURL
curl -X POST https://api.seo1.us/v1/keyword-research \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "phrase": "running shoes",
    "database": "us",
    "match_type": "broad",
    "max_pages": 2,
    "filters": { "volume_min": 500, "kd_max": 40 }
  }'
POST /v1/keyword-research/csv CSV

The same keyword data, streamed back as text/csv. Takes the same body minus page/page_size; defaults to max_pages: 10.

bash
curl -o keywords.csv -X POST https://api.seo1.us/v1/keyword-research/csv \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"phrase": "running shoes", "database": "us", "max_pages": 10}'

Response headers include X-Total-Available and X-Rows-Returned so you can tell how much data was available versus returned.

Top countries

Where a domain has organic traffic, broken down by Semrush database. The fastest way to pick the right database before a ranked-keywords call.

POST /v1/top-countries

Returns a sorted countries[] array with per-database traffic and positions.

Body parameters

FieldTypeDefaultNotes
domainrequiredstringDomain to inspect.
cURL
curl -X POST https://api.seo1.us/v1/top-countries \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

Keyword gap

Compare the organic keyword profiles of 2–5 domains — find keywords competitors rank for that you don't.

POST /v1/keyword-gap

Native-first with automatic composite fallback. The native upstream (NoxTools) is tried first; it is often Semrush rate-limited. When native is unavailable the gateway transparently computes the gap by diffing ranked-keywords (served by all 5 providers), so the endpoint stays reliable. The path used is reported via the X-Gap-Source header (native | composite) and the source field (composite only). Quota counts once per request regardless of internal calls.

Body parameters

FieldTypeDefaultNotes
targetsrequiredstring[]2–5 domains. First is "your" domain. Alias: target + competitors.
databasestringusCountry code — see country codes.
typeint7Native only. 1=Shared 2=Missing 3=Weak 4=Strong 5=Untapped 6=Unique 7=All
limitint100Rows to return (composite caps at 200).
offsetint0Pagination offset (native only).
order_bystringvolumeField to sort by (native only).
order_dirstringdescasc | desc (native only).
filtersstring""Raw Semrush filter string (native only).
min_volumeint0Composite only — drop gap keywords below this volume.
cURL
curl -X POST https://api.seo1.us/v1/keyword-gap \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"targets": ["backlinko.com", "ahrefs.com"], "database": "us", "type": 2, "limit": 2}'

Native response (X-Gap-Source: native) — passed through verbatim. Each row in keywords[] includes keyword, volume, keyword_difficulty, cpc, competition, intents, plus positions and urls (per-target, in targets order).

Composite response (X-Gap-Source: composite) — the keywords any competitor ranks for that the target (first domain) does not. Adds top-level source, target, competitors and a note. Each row in keywords[] includes phrase, volume, keyword_difficulty, cpc, intents, plus competitor, competitor_position (best/lowest) and url. If the target's own ranked-keywords lookup fails, the response adds target_ranked_unavailable: true (diff is incomplete). If ranked-keywords fails for every domain, you get the standard 503.

Bulk keyword volume

Look up search volume and metrics for up to 2000 keywords in a single request.

POST /v1/bulk-keyword-volume

Returns a keywords[] array plus total_volume.

Body parameters

FieldTypeDefaultNotes
keywordsrequiredstring[]1–2000 keyword phrases.
databasestringusCountry code — see country codes.
cURL
curl -X POST https://api.seo1.us/v1/bulk-keyword-volume \
  -H "X-API-Key: smg_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["cnc machining", "metal fabrication"], "database": "us"}'

Each row in keywords[] includes phrase, volume, cpc, competition, keyword_difficulty, intents, serp_features, trends, and click_potential. Top-level: requested, returned, total_volume.

Account usage

Your key's current usage against its limits. Authenticated, but does not count toward your quota.

GET /v1/me/usage
bash
curl https://api.seo1.us/v1/me/usage \
  -H "X-API-Key: smg_your_api_key"
json — response
{
  "key_name": "Acme Corp",
  "requests_today": 137,
  "daily_quota": 5000,
  "rate_limit_per_min": 60,
  "requests_total": 18452
}

Health

A public status check — no API key required. Returns overall status and a per-source summary.

GET /health public
bash
curl https://api.seo1.us/health
json — response
{
  "status": "ok",
  "providers": [
    {"name": "a", "enabled": true, "healthy": true,  "breaker": "closed", "in_flight": 0},
    {"name": "b", "enabled": true, "healthy": true,  "breaker": "closed", "in_flight": 2}
  ]
}

As long as status is ok and at least one source is healthy, the API serves requests — failover handles the rest.

Country (database) codes

Endpoints that take a database accept standard Semrush country codes. Common ones:

codes
us  uk  au  ca  in  jp  nl  sg  my  de  fr  es  it  br  mx  se  hk  id

If you're unsure which database has data for a domain, call /v1/top-countries first.


Semrush Gateway · unified Semrush data API · automatic failover across multiple sources. Questions or a new key? Contact the gateway administrator.