Skip to main content

Errors & rate limits

Every Ranktracker API error comes back in a predictable shape, with an HTTP status code you can branch on and a machine-readable code you can log. This guide covers the error envelope, what each status code means, and how to handle throttling.

If you haven't made a request yet, start with the Quickstart and Authentication guides.

The error envelope

Errors use a JSON:API-style envelope: a top-level errors array, where each entry has a code, a numeric status, and a human-readable detail.

{
"errors": [
{
"code": "over_quota",
"status": 402,
"detail": "You have reached your plan's keyword limit."
}
]
}

Key points:

  • The HTTP response status matches the status field on the error.
  • A single response can carry multiple entries in errors — for example, a 422 validation failure may return one entry per invalid field. Iterate the array rather than reading only errors[0].
  • Some errors include extra fields. SERP errors (see /rest-api/serp), for instance, add an id alongside code / status / detail. Treat unknown fields as additive and don't rely on their presence.
note

A handful of failures — notably a disabled or missing API key — are returned as a plain 403 Forbidden HTML body rather than the JSON envelope. Always branch on the HTTP status code first, then parse the JSON body when one is present.

Status codes

StatusMeaningWhen it happens
400Bad requestA required parameter is missing or malformed (for example, a create call sent without its wrapping resource object).
402Payment requiredThe request would exceed your plan's quota — for example, adding keywords beyond your keyword or data-row allowance. Nothing is created.
403ForbiddenYour API key is missing/invalid, or your plan does not have API access enabled.
404Not foundThe resource doesn't exist, or it belongs to another account. Lookups are account-scoped, so a foreign UUID looks identical to one that was never created.
422Unprocessable entityThe request was well-formed but failed validation — an invalid attribute, an empty required array, or (for SERP) data that isn't ready yet.
503Service unavailableThe request was throttled (rate-limited). Retry with backoff — see Rate limiting.

403 — plan access and authentication

A 403 means one of two things:

  • Authentication failed — the Authorization header is missing or the key is invalid. See Authentication for how keys are sent.
  • API access is disabled for your plan — the key is valid but your plan isn't entitled to use the REST API.

You can check whether API access is enabled for your account without triggering an error: GET /v1/account/usage returns an apiEnabled flag in its attributes.

curl https://api.ranktracker.com/v1/account/usage \
-H "Authorization: tkn_usr_your_api_key_here"
{
"data": {
"id": "…",
"type": "usage",
"attributes": {
"apiEnabled": true,
"keywords": { "usage": 842, "limit": 1000, "remaining": 158 },
"dataRows": { "usage": 12050, "limit": 20000, "remaining": 7950 }
}
}
}

If apiEnabled is false, upgrade or enable API access in the Ranktracker app at https://app.ranktracker.com. See /rest-api/account for the full response shape.

402 — over quota

Adding keywords tracks the cartesian product of words and search_engines, so a single create call can consume many keyword and data-row credits. If the request would push you past your plan's keyword or data-row allowance, the API responds 402 and links nothing — the operation is all-or-nothing, so you never end up with a partially-applied batch.

{
"errors": [
{
"code": "over_quota",
"status": 402,
"detail": "Tracking these keywords would exceed your plan's data-row limit."
}
]
}

To avoid 402s, check remaining allowance before a large add — the remaining values from GET /v1/account/usage tell you how much headroom you have. See the Usage & quota guide for the full workflow, and Bulk operations for batching keywords safely.

422 — validation

A 422 means the request reached the resource but failed validation. Read every entry in the errors array to surface each problem — creating a domain with a bad match_type, bulk-tagging with an empty keyword_uuids array, or requesting a SERP that hasn't been captured yet all return 422.

{
"errors": [
{
"code": "invalid",
"status": 422,
"detail": "match_type is not included in the list"
}
]
}

Rate limiting

The API throttles bursts of traffic. When you're throttled, the request is rejected with:

HTTP/1.1 503 Service Unavailable

:::warning No rate-limit headers yet There are currently no RateLimit-* headers on responses — you can't read a remaining-request count or a reset timestamp from the API. Treat a 503 itself as the signal to slow down. :::

Handling 503 with exponential backoff

When you receive a 503, wait and retry, increasing the delay after each consecutive failure (add a little random jitter so parallel clients don't retry in lockstep). Cap the number of attempts so a persistent outage doesn't loop forever, and only retry idempotent-safe work — a GET is always safe to retry; for writes, be aware a 503 after a partial send is rare but possible.

import time, random, requests

BASE_URL = "https://api.ranktracker.com"
HEADERS = {"Authorization": "tkn_usr_your_api_key_here"}

def get_with_backoff(path, max_retries=5):
for attempt in range(max_retries):
resp = requests.get(f"{BASE_URL}{path}", headers=HEADERS)
if resp.status_code != 503:
resp.raise_for_status()
return resp.json()
# Throttled: back off 1s, 2s, 4s, 8s… plus jitter.
delay = (2 ** attempt) + random.random()
time.sleep(delay)
raise RuntimeError(f"Still throttled after {max_retries} attempts")

domains = get_with_backoff("/v1/domains")
const BASE_URL = "https://api.ranktracker.com";
const HEADERS = { Authorization: "tkn_usr_your_api_key_here" };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function getWithBackoff(path, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await fetch(`${BASE_URL}${path}`, { headers: HEADERS });
if (resp.status !== 503) {
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json();
}
// Throttled: back off 1s, 2s, 4s, 8s… plus jitter.
const delay = 2 ** attempt * 1000 + Math.random() * 1000;
await sleep(delay);
}
throw new Error(`Still throttled after ${maxRetries} attempts`);
}

const domains = await getWithBackoff("/v1/domains");

:::tip Stay well under the limit The cheapest way to avoid 503s is to not hit them: page through large result sets with a sensible per_page (see Pagination), batch writes where an endpoint supports it (see Bulk operations), and cache responses that don't change on every request. :::

Quick reference

  • Branch on the HTTP status first, then parse the JSON errors array.
  • 400 / 422 are your bug — fix the request; they won't succeed on retry.
  • 402 is a quota wall — check Usage & quota before large adds.
  • 403 is auth or plan access — verify your key and apiEnabled.
  • 404 is account-scoped — a foreign UUID looks the same as a missing one.
  • 503 is transient — retry with exponential backoff and jitter.