RankParse

Errors

The error body shape, every error code the RankParse API returns, whether you are charged, and how to retry safely.

Failed requests return a non-2xx HTTP status and a JSON body with a stable machine-readable code. Branch on code, not on the human-readable text, which can change.

Error body

402 Payment Required
{
  "error": "Payment Required",
  "code": "insufficient_credits",
  "message": "Add credits at https://rankparse.com/pricing"
}
FieldMeaning
codeStable identifier. Always present on errors the API raises deliberately.
errorShort label, normally the HTTP status text (Bad Request, Too Many Requests).
messageHuman-readable explanation, often with the fix.
retry_afterSeconds to wait. Present on daily quota errors and on retryable 503 errors.
retrytrue or false on data store 503 errors, telling you whether an identical retry can succeed.

Variations you should handle:

  • Live fetch endpoints (page-seo, tech-stack, site-health, schema-markup, sitemap) return error as a short description such as Invalid URL and may omit message.
  • Approval inbox, agent task and some outreach errors set error to the same value as code.
  • Unexpected server faults return { "error": "Internal Server Error", "message": "..." } with no code.
  • Unknown routes return a plain text 404 Not Found, not JSON.

A domain with no data is never an error. It returns 200 with data: []. See Response format.

Order of checks

Requests are checked in this order, and the first failure wins. This tells you whether you were charged.

  1. Authentication (401)
  2. Role permission (403 capability_missing)
  3. Per-key rate limit (429 rate_limited)
  4. Input validation. Live endpoints validate here, before any charge.
  5. Credit check and charge (402)
  6. The work itself. If it throws (a data store timeout, an upstream outage), the charge is refunded.

Are you charged?

FailureCharged?
401, 403, 429No. These happen before billing.
402No. The charge itself was refused.
Validation 400 (invalid or missing parameters)No. Validated before any charge.
503 query_timeout, upstream_error, deterministic_timeoutNo. The charge is refunded automatically.
Live fetch failures (fetch_failed, timeout, private_ip, response_too_large)No. Refunded.
429 and 503 on page-performance (quota_exceeded, user_quota_exceeded, psi_unavailable)No. page-performance charges only after Google returns a result.
IndexNow errors (any non-2xx from POST /v1/indexnow)No. Refunded.
Free routes (rank tracking, Search Console, Google Ads, approval inbox, outreach)Never charged.
Partial resultsYes, at the normal price, except domain-authority when the score is unavailable.

Error codes

Authentication and permissions

StatusCodeMeaningWhat to do
401invalid_api_keyThe X-API-Key header is missing, the key is unknown or revoked, or its workspace was deleted.Send a valid key. See Authentication.
401internal_auth_invalidA credential reserved for RankParse's own services was rejected.Not for public use. Use an API key.
403capability_missingYour role in the workspace does not allow this action (for example a member trying to buy credits or manage billing).Ask a workspace owner or admin, or use a key from a role that has access.
404key_not_foundDELETE /v1/keys/:id named a key that does not exist, is already revoked, or belongs to someone else.List keys with GET /v1/keys and retry with a current id.
404user_not_foundThe authenticated user record could not be found.Contact support.

Credits and billing

StatusCodeMeaningWhat to do
402insufficient_creditsThe balance is lower than the call costs.Buy credits or enable auto recharge. See Credits. Retrying does not help until the balance rises.
402spend_cap_reachedThe workspace hit its monthly spend cap.Raise the cap in Billing, Credits or wait for the next calendar month (UTC).
400invalid_creditsPOST /v1/checkout was given fewer than 100 credits or a non-integer.Send an integer of at least 100.
500checkout_failedStripe checkout could not be created.Retry.

Validation (all endpoints)

StatusCodeMeaningWhat to do
400invalid_domaindomain is missing or is not a bare hostname (letters, digits, dots and hyphens, at least one dot, up to 253 characters). https://stripe.com is rejected.Send a hostname such as stripe.com.
400invalid_domainsdomains, domain_a, domain_b or vs is missing or has too few valid domains (domain-overlap needs at least 2). On batch, domains must be a non-empty array with at least one valid entry.Fix the parameter.
400too_many_domainsMore domains than allowed (domain-overlap allows 5, batch allows 50).Split the request.
400same_domaincompetitor-gap was called with domain equal to vs.Compare two different domains.
400invalid_sortsort on backlinks is not importance or recent.Use a supported value.
400invalid_from_domainfrom_domain on backlinks is not a valid domain.Fix the value.
400invalid_link_typelink_type on backlinks has invalid characters.Use letters, digits, _ or -, up to 32 characters.
400invalid_urlurl is missing or is not a valid public http or https URL (no credentials, no # fragment, no surrounding whitespace). Also returned when domain is invalid on the live endpoints tech-stack, site-health and sitemap.Send a full public URL, or a bare hostname for domain endpoints.
400private_ipThe URL or domain points at a private or reserved address.Use a publicly reachable host.
400missing_platformplatform-domains was called without platform.Add platform.
400unknown_platformplatform is not one of the supported platforms. The message lists them.Use a listed platform.
400filter_not_availableregistered_after, registered_before or country was sent to platform-domains.Remove the filter.

Rate and quota limits

StatusCodeMeaningWhat to do
429rate_limitedMore than 60 requests in the last minute for this key.Back off and retry. See Rate limiting.
429quota_exceededOn page-performance: the shared daily PageSpeed quota is used up. retry_after is the seconds until 00:00 UTC. On /v1/tools/*: the free daily limit for your IP or account is used up, with reset_at as a millisecond timestamp.Wait for the reset.
429user_quota_exceededYou made 50 page-performance calls today (UTC), cached calls included.Try again after 00:00 UTC.
429tools_quota_exceededThe free page-speed tool's daily capacity is used up.Try again tomorrow.
429google_ads_user_quota_exceededYou used your 100 Google Ads keyword calls today (UTC).Wait until 00:00 UTC (retry_after).
429google_ads_daily_quota_exceededThe service-wide Google Ads allowance is used up for today.Wait until 00:00 UTC (retry_after).

Data store and live fetch

StatusCodeMeaningWhat to do
503query_timeoutThe data query timed out. Usually transient. Includes retry: true, retry_after: 3 and a Retry-After: 3 header.Retry the same request. For very large domains, lower limit.
503upstream_errorThe data store was briefly unavailable. Same retry fields as above.Retry the same request after a few seconds.
503deterministic_timeoutThis exact input is too expensive to finish in budget (seen on competitor-gap). retry: false.Do not retry unchanged. Narrow the request or lower limit.
503fetch_failedThe target site could not be fetched (DNS failure, connection error, too many redirects, blocked).Check the site is public and online, then retry later.
503timeoutThe target site did not respond in time.Retry later.
400fetch_failed, timeoutOn site-health, fetch problems are reported as 400 instead of 503. Refunded.Same advice.
400response_too_largeThe target page exceeded the size the API reads. Refunded.Choose a lighter page.
503psi_unavailableGoogle PageSpeed Insights failed or is unavailable.Retry later. You were not charged.
500tool_failedA free tool failed in an unclassified way.Retry.

Free tools

The /v1/tools/* routes are built for the RankParse website and require a captcha token plus a per-IP or per-account daily allowance. See Free tools.

StatusCodeMeaningWhat to do
400invalid_inputThe domain or URL input is missing or invalid.Fix the input.
403turnstile_failedThe captcha challenge failed.Refresh and try again.
429quota_exceededFree daily limit reached. The body has reset_at.Sign up or wait for the reset.

Rank tracking

StatusCodeMeaningWhat to do
404rank_tracking_site_not_foundNo rank tracking site with that site_id belongs to this workspace.List sites with GET /v1/rank-tracking/sites.
404rank_tracking_keyword_not_foundNo tracked keyword with that keyword_id on this site.List keywords first.
400invalid_rank_tracking_engineengine is not valid (all, google, bing, yandex; history accepts only a single engine).Use a supported value.
400rank_tracking_provider_disabledThe engine is valid but not enabled for this account.Pick an enabled engine (see enabled_engines on the rankings response).
400invalid_rank_tracking_periodperiod_days is not an integer in the allowed range (1 to 90).Fix the value.
400invalid_rank_tracking_datestart_date or end_date is missing, not YYYY-MM-DD, out of order, or spans more than 400 days.Fix the dates.
400invalid_rank_tracking_dimensiondimension is not total or device.Fix the value.
400invalid_rank_tracking_alert_settingsA PATCH to alert settings had an invalid field (move_threshold outside 1 to 50, bad email_override, wrong types).Fix the field named in message.
400rank_tracking_property_not_attachedHistory was requested for an engine with no primary property attached to the site.Attach a property in the dashboard.

Google Search Console

StatusCodeMeaningWhat to do
400gsc_not_connectedNo Google Search Console connection on this workspace.Connect it under Integrations.
401gsc_token_expiredThe Google connection expired or was revoked.Reconnect in the dashboard.
403gsc_access_deniedNo access to that property, or the URL does not match the verified property.Use the exact property string from Search Console.
404gsc_property_not_foundThe property is not visible to any connected Google account.Check the site value and connected accounts.
429gsc_quota_exceededGoogle's Search Console API quota was exceeded.Retry later.
502gsc_token_refresh_failedGoogle would not refresh the access token.Retry shortly.
502gsc_api_errorGoogle returned an unexpected error.Retry.
503gsc_not_configuredThe integration is not configured on the server.Contact support.
400missing_site, missing_params, missing_dates, invalid_date, invalid_dimensions, invalid_search_type, invalid_param, conflicting_filtersA query parameter is missing or invalid. The message names it. filterPage and filterPageContains cannot be combined.Fix the parameter.
StatusCodeMeaningWhat to do
400google_ads_not_connectedGoogle Ads is not connected.Connect it under Integrations.
400google_ads_no_account_selectedConnected, but no Ads account chosen.Select an account in the dashboard.
400google_ads_invalid_requestGoogle rejected the request (often a bad language or location resource name).Use names like languageConstants/1000 and geoTargetConstants/2840.
400invalid_languagelanguage is not a language resource name.Use languageConstants/<id>.
400invalid_geo_targetgeo_target_constants is not an array of location resource names.Use geoTargetConstants/<id>.
400invalid_jsonThe body is not valid JSON.Send valid JSON.
400missing_seedkeyword-ideas needs keywords and/or url.Provide a seed.
400missing_keywordskeyword-metrics needs a keywords array.Provide keywords.
401google_ads_token_expiredThe Google Ads connection expired.Reconnect in the dashboard.
403google_ads_developer_token_not_approvedRankParse's Google Ads access level is still pending, so live data is unavailable. Nothing is wrong with your account.Try again later.
403google_ads_customer_not_enabledThe selected Ads account is inactive (never set up or deactivated).Pick another account or activate this one.
403google_ads_access_deniedGoogle denied the request, for example a manager (MCC) account was selected. Manager accounts are not supported.Select a regular Ads account.
404google_ads_account_not_foundThe selected account was not found.Reselect the account.
429google_ads_quota_exceededGoogle's own API quota was exceeded.Retry later.
502google_ads_token_refresh_failedThe access token could not be refreshed.Retry shortly.
502google_ads_api_errorGoogle Ads returned an unexpected error.Retry.
503google_ads_not_configuredThe integration is not configured on the server.Contact support.

The Google Ads daily limits (google_ads_user_quota_exceeded, google_ads_daily_quota_exceeded) are listed under rate and quota limits above. A call counts against them only after Google returns a successful answer.

IndexNow

StatusCodeMeaningWhat to do
400indexnow_invalid_urlurls is missing, empty, not an array of strings, contains a non-public or non-http(s) URL, or uses an explicit port.Send full public URLs on the default port.
400indexnow_too_many_urlsMore than 1000 URLs.Split into batches of 1000.
400indexnow_host_mismatchThe URLs span more than one host.Submit one host per call.
400indexnow_host_not_a_websiteThe URLs' host is not a website in your workspace.Add the site in the dashboard first.
400indexnow_key_not_foundNo key exists for the host.Create one with POST /v1/indexnow/key?domain=.
400indexnow_key_not_verifiedThe key file is not hosted at its key location (30 second check).Host a text file containing exactly the key, then retry.
400indexnow_invalid_requestIndexNow rejected the submission as malformed.Check the URL list.
403indexnow_key_rejectedIndexNow no longer accepts the key. The cached verification is cleared.Confirm the key file is still hosted, then retry.
422indexnow_url_mismatchThe URLs do not belong to the submitted host or the key does not match.Check the host and key.
429indexnow_rate_limitedIndexNow rate limit reached.Slow down and retry later.
503indexnow_unavailableIndexNow is unreachable or returned an unexpected status.Retry later.

Approval inbox and agent tasks

These routes (/v1/proposals, /v1/websites, /v1/activity, /v1/agent-tasks, /v1/client-report) set error equal to code. See Approval inbox.

StatusCodeMeaningWhat to do
400invalid_proposal_inputThe body, kind, site_id, client_request_id or status filter is invalid. The message says which.Fix the input.
400kind_not_availableThat proposal kind is not available.Use indexnow_submit or outreach_send.
400invalid_activity_inputThe activity note is malformed.Provide summary and optional fields as documented.
400invalid_client_report_inputsite_id is missing or week_start is not a Monday date that is not in the future.Fix the input.
400invalid_task_summaryTask summary is missing or too long.Fix the summary.
400site_host_mismatchThe URLs do not belong to the website named.Use URLs on that website.
400gmail_not_connectedA proposed or launched outreach send needs Gmail connected.Connect Gmail under Integrations.
400campaign_site_mismatch, sending_provider_unsupported, no_contactsThe campaign cannot be proposed as sent.See Outreach.
403approval_inbox_not_enabledThe inbox is not enabled for this account yet.Contact support.
403action_disabled_by_policyThe user turned this action off in website settings.Ask the user.
403invalid_request_sourceThe proposal source is not allowed for this caller.Use the documented request.
404site_not_foundNo website with that id in this workspace.Call GET /v1/websites.
404proposal_not_foundNo proposal with that id.List proposals.
404task_not_foundNo agent task with that id.List tasks.
404campaign_not_foundNo campaign with that id in this workspace.List campaigns.
404tracked_query_not_foundNo tracked query with that id on the website.Check the id.
409task_not_claimableThe task is done, expired or claimed by another agent.Pick another task.
409change_in_progressA different change was already approved and is running.Wait, then send again.
429too_many_pending_proposalsA website already has 25 proposals waiting.Ask the user to review the inbox.
429daily_proposal_limit_reached100 new proposals were created today.Try again tomorrow.
429activity_limit_reached200 activity notes were logged today.Try again tomorrow.

Outreach

StatusCodeMeaningWhat to do
400invalid_jsonThe body is not valid JSON.Send valid JSON.
400invalid_nameCampaign name is missing or longer than 200 characters.Fix the name.
400invalid_domainyour_domain is missing.Add it.
400invalid_site_idsite_id is not a non-empty string.Fix or omit it.
400no_contacts, too_many_contactsThe contacts array is empty or over the per-call limit.Send a valid batch.
400invalid_send_cap, invalid_auto_send, nothing_to_updateSending settings were invalid or empty. max_sends_per_day must be 1 to 100.Fix the body.
400gmail_not_connected, sending_provider_unsupportedGmail is required for sending from the API.Connect Gmail.
404campaign_not_found, site_not_foundThe campaign or website does not belong to this workspace.List yours.
409campaign_not_editable, campaign_launch_in_progressThe campaign state does not allow this now.Retry shortly.

Handling errors

Which errors to retry

ClassExamplesRetry?
Transient server errorsquery_timeout, upstream_error, 500, 502, 503 psi_unavailable, indexnow_unavailableYes. Exponential backoff with jitter, a handful of attempts.
Rate limitrate_limitedYes, after a pause. Sliding window of 60 seconds.
Daily quotasquota_exceeded, user_quota_exceeded, google_ads_*_quota_exceededNot until the reset time in retry_after (00:00 UTC).
Target site problemsfetch_failed, timeoutMaybe once or twice. The site may be down.
Creditsinsufficient_credits, spend_cap_reachedNo. Fix the balance or the cap first.
Client errorsOther 400, 403, 404, 409, 422No. Change the request.
Authinvalid_api_keyNo. Fix the key.
Non-retryable data errorsdeterministic_timeout (retry: false)No. Narrow the request.

Retrying a request that failed with a refunded error is safe: you were not charged for the failed attempt.

Code samples

curl -i "https://api.rankparse.com/v1/domain-authority?domain=github.com" \
  -H "X-API-Key: rp_your_key"
# Inspect the status line, then the JSON body's "code" field.
import os
import random
import time

import requests

API = "https://api.rankparse.com/v1"
HEADERS = {"X-API-Key": os.environ["RANKPARSE_API_KEY"]}

RETRYABLE = {"rate_limited", "query_timeout", "upstream_error", "psi_unavailable",
             "indexnow_unavailable", "timeout", "fetch_failed"}

class RankParseError(Exception):
    def __init__(self, status, code, message):
        super().__init__(f"{status} {code}: {message}")
        self.status, self.code, self.message = status, code, message

def call(path, params=None, max_attempts=4):
    for attempt in range(max_attempts):
        resp = requests.get(f"{API}{path}", params=params, headers=HEADERS, timeout=60)
        if resp.ok:
            return resp.json()

        try:
            body = resp.json()
        except ValueError:
            body = {}
        code = body.get("code", "unknown")
        message = body.get("message") or body.get("error", "")

        retryable = (code in RETRYABLE or resp.status_code in (500, 502, 503)) and body.get("retry") is not False
        if not retryable or attempt == max_attempts - 1:
            raise RankParseError(resp.status_code, code, message)

        # Honor retry_after when present, otherwise exponential backoff with jitter.
        delay = body.get("retry_after") or min(2 ** attempt, 30)
        if delay <= 300:  # do not sleep until tomorrow on a daily quota
            time.sleep(delay + random.random())
        else:
            raise RankParseError(resp.status_code, code, message)

try:
    print(call("/domain-authority", {"domain": "github.com"})["data"])
except RankParseError as err:
    if err.code == "insufficient_credits":
        print("Out of credits: https://rankparse.com/pricing")
    else:
        raise
const API = "https://api.rankparse.com/v1";
const HEADERS = { "X-API-Key": process.env.RANKPARSE_API_KEY };

const RETRYABLE = new Set([
  "rate_limited", "query_timeout", "upstream_error", "psi_unavailable",
  "indexnow_unavailable", "timeout", "fetch_failed",
]);

class RankParseError extends Error {
  constructor(status, code, message) {
    super(`${status} ${code}: ${message}`);
    this.status = status;
    this.code = code;
  }
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function call(path, params = {}, maxAttempts = 4) {
  const url = `${API}${path}?${new URLSearchParams(params)}`;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const resp = await fetch(url, { headers: HEADERS });
    if (resp.ok) return resp.json();

    const body = await resp.json().catch(() => ({}));
    const code = body.code ?? "unknown";
    const message = body.message ?? body.error ?? "";

    const retryable =
      (RETRYABLE.has(code) || [500, 502, 503].includes(resp.status)) && body.retry !== false;
    const delay = body.retry_after ?? Math.min(2 ** attempt, 30);
    // Never sleep until tomorrow on a daily quota (retry_after can be hours).
    if (!retryable || attempt === maxAttempts - 1 || delay > 300) {
      throw new RankParseError(resp.status, code, message);
    }
    await sleep((delay + Math.random()) * 1000);
  }
}

try {
  const result = await call("/domain-authority", { domain: "github.com" });
  console.log(result.data);
} catch (err) {
  if (err.code === "insufficient_credits") {
    console.error("Out of credits: https://rankparse.com/pricing");
  } else {
    throw err;
  }
}

On this page