Skip to main content

Core concepts

This guide is the conceptual backbone for the rest of the documentation. It explains how Ranktracker's resources relate to each other, how they map onto URLs, and the shape of every request and response. Once these ideas are clear, the endpoint-specific guides and the API reference will read as variations on the same handful of patterns.

Every example authenticates with an API key sent verbatim in the Authorization header — no Bearer prefix. Create and manage keys in the Ranktracker app. See the Authentication guide for the details.

The resource model

Your data forms a simple hierarchy rooted at your account:

  • An account owns domains and account-wide tags, and exposes usage (your plan's quota).
  • A domain is a site you track. Each domain has competitors and keywords, plus its own ranking history and share of voice.
  • A keyword is a word tracked on a domain for a specific search engine, location, language, and device. Each keyword carries SERP data (its rankings and the raw search-results page) and can be labelled with tags.
  • A tag is an account-level label. You attach tags to keywords; the same tag can be reused across many keywords and domains.

Read it top-down: your account has domains, a domain has competitors and keywords, a keyword has SERP data and tags, and account/usage tells you how much of your plan you have consumed.

account
├── usage (plan quota: keywords, data rows)
├── tags (account-wide labels)
└── domains
├── history (ranking & visibility over time)
├── share-of-voice
├── competitors
└── keywords
├── serp (latest search results)
├── serp-html (cached SERP page)
└── tags (taggings for this keyword)

:::note Everything is scoped to your account The API only ever returns data your API key's account owns. A domain, keyword, or tag that belongs to another account is invisible to you — you get a 404, as though it did not exist, rather than a 403. See Errors & rate limits. :::

IDs are UUIDs

Every resource is identified by a UUID string — for example 3f2a9c7e-1b6d-4e2a-9f10-8c5b0a1d2e3f. These UUIDs appear as the id on each resource and are the values you drop into URL paths. There are no auto-incrementing integer IDs in the public API; always treat an ID as an opaque string and never try to parse or predict one.

You collect a resource's UUID from the response when you create or list it, then use it to address that resource directly:

curl https://api.ranktracker.com/v1/domains/3f2a9c7e-1b6d-4e2a-9f10-8c5b0a1d2e3f \
-H "Authorization: tkn_usr_your_api_key_here"

The JSON:API envelope

Every response body is wrapped in a JSON:API-style envelope. The rules are consistent across the whole API:

  • The payload lives under a top-level data key.
  • A single resource is an object; a collection is an array.
  • Each resource object has an id (its UUID), a type (the resource kind), and an attributes object holding its fields.
  • Attribute keys are camelCase (for example projectName, matchType, createdAt).

A single resource

GET /v1/domains/{uuid} returns one object under data:

{
"data": {
"id": "3f2a9c7e-1b6d-4e2a-9f10-8c5b0a1d2e3f",
"type": "domain",
"attributes": {
"domain": "https://www.example.com",
"scheme": "https",
"host": "www.example.com",
"matchType": "any",
"projectName": "Example Site",
"colour": "#4f46e5",
"createdAt": "2026-01-14T09:12:44Z",
"updatedAt": "2026-06-30T18:03:11Z"
}
}
}

A list of resources

List endpoints return an array under data. GET /v1/domains:

{
"data": [
{
"id": "3f2a9c7e-1b6d-4e2a-9f10-8c5b0a1d2e3f",
"type": "domain",
"attributes": {
"host": "www.example.com",
"projectName": "Example Site",
"matchType": "any"
}
},
{
"id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"type": "domain",
"attributes": {
"host": "shop.example.com",
"projectName": "Example Shop",
"matchType": "domain"
}
}
]
}

:::tip Request bodies are not camelCase Responses use camelCase, but request bodies for writes use snake_case and are nested under a resource key. Creating a domain expects { "domain": { "host": "...", "match_type": "..." } }, not matchType. Each write guide shows the exact body it wants — follow those rather than mirroring the response shape. :::

Error envelope

Errors follow the same envelope idea, but under an errors array instead of data. Each entry carries a code, status, and human-readable detail:

{
"errors": [
{
"code": "unprocessable_entity",
"status": 422,
"detail": "Host can't be blank"
}
]
}

The Errors & rate limits guide covers every status you may see and how to handle it.

HTTP verbs and status codes

The API uses standard HTTP verbs, and the verb tells you the intent:

VerbMeaningTypical success
GETRead a resource or list200 OK
POSTCreate a resource201 Created
PATCHUpdate part of a resource200 OK
DELETERemove (usually soft-delete) a resource204 No Content

A DELETE returns 204 with an empty body. Deletes are generally idempotent — deleting something already gone still returns 204 rather than an error, so retries are safe.

The status codes you will encounter across the API:

StatusWhen it happens
200 OKSuccessful read or update
201 CreatedA POST created the resource
204 No ContentA DELETE succeeded (empty body)
400 Bad RequestRequired parameters are missing entirely
402 Payment RequiredThe write would exceed your plan's keyword or data-row quota
403 ForbiddenYour plan does not have API access enabled, or the action is forbidden
404 Not FoundThe resource does not exist, or is not in your account
422 Unprocessable EntityValidation failed (e.g. a bad or missing attribute)
503 Service UnavailableThe request was throttled — retry with backoff

:::note Throttling returns 503 When you are rate-limited the API currently responds with 503, and there are no RateLimit-* headers yet. Treat a 503 as transient and retry with exponential backoff. Full detail is in Errors & rate limits. :::

Nested resources map to nested paths

The URL structure mirrors the resource hierarchy. A resource that only exists in the context of a parent lives under that parent's path, addressed by the parent's UUID.

Top-level resources sit directly under /v1:

GET /v1/domains List your domains
POST /v1/domains Create a domain
GET /v1/domains/{uuid} Retrieve one domain
PATCH /v1/domains/{uuid} Update a domain
DELETE /v1/domains/{uuid} Delete a domain

GET /v1/tags List account-wide tags
POST /v1/tags Create a tag
GET /v1/account/usage Read plan usage and quota

Competitors and keywords belong to a domain, so they nest under that domain's UUID:

GET /v1/domains/{domain_uuid}/competitors
POST /v1/domains/{domain_uuid}/competitors
GET /v1/domains/{domain_uuid}/competitors/{uuid}

GET /v1/domains/{domain_uuid}/keywords
POST /v1/domains/{domain_uuid}/keywords
GET /v1/domains/{domain_uuid}/keywords/{uuid}

SERP data and per-keyword taggings belong to a keyword, so they nest one level deeper still:

GET /v1/domains/{domain_uuid}/keywords/{keyword_uuid}/serp
GET /v1/domains/{domain_uuid}/keywords/{keyword_uuid}/serp-html
POST /v1/domains/{domain_uuid}/keywords/{keyword_uuid}/tags/{tag_uuid}
DELETE /v1/domains/{domain_uuid}/keywords/{keyword_uuid}/tags/{tag_uuid}

To address a nested resource you supply every UUID on the path. Tagging one keyword needs the domain, the keyword, and the tag:

curl -X POST \
https://api.ranktracker.com/v1/domains/DOMAIN_UUID/keywords/KEYWORD_UUID/tags/TAG_UUID \
-H "Authorization: tkn_usr_your_api_key_here"

Tags themselves are account-wide (/v1/tags), but taggings — the link between a tag and a keyword — are per-keyword and live under the keyword's path. You can attach a tag to many keywords at once with the bulk endpoint, POST /v1/domains/{domain_uuid}/keywords/taggings. There is currently no bulk untag endpoint; to untag in bulk, remove the tagging from each keyword individually. See Bulk operations and Keyword tags.

A quick tour of each resource

The reference documents every field; this is the mental model.

Domains

Domains support full CRUD. When you create one, its host, scheme, and matchType define the identity of the site and are immutable afterwards — to change them you delete the domain and create a new one. Fields like projectName can be updated with PATCH.

Two extra read endpoints expose time-series data for a domain, GET /v1/domains/{uuid}/history and GET /v1/domains/{uuid}/share-of-voice. Both accept start_date and end_date as YYYY-MM-DD strings and a device filter of all, desktop, or mobile. The maximum range is 365 days. See Domains.

curl "https://api.ranktracker.com/v1/domains/DOMAIN_UUID/history?start_date=2026-06-01&end_date=2026-06-30&device=all" \
-H "Authorization: tkn_usr_your_api_key_here"

Competitors

Competitors are the rival sites tracked against one of your domains. They support full CRUD and are always nested under a domain. See Competitors.

Keywords

Keywords are nested under a domain. Creating them is the one place the API does real fan-out: a POST tracks the cartesian product of the words array and the search_engines array, so ten words across three search-engine configurations create thirty tracked keywords in one call. PATCH toggles the tracked flag to pause or resume a keyword, and DELETE stops tracking it (a soft-delete of the tracking link). If a create would push you over your plan's keyword or data-row quota, the whole request is rejected with 402 and nothing is tracked. See Keywords.

SERP

Each keyword exposes its search-results data. GET .../serp returns the latest parsed SERP (organic results, features, and rankings), and GET .../serp-html returns the cached raw HTML of the results page. See SERP.

Tags and taggings

A tag (/v1/tags) is an account-wide label with full CRUD. A tagging is the association between a tag and a keyword, managed under the keyword's path (individually) or via the bulk-tag endpoint. See Tags and Keyword tags.

Account usage

GET /v1/account/usage reports your plan's keyword and data-row usage and limits — the same quota that produces a 402 on keyword creates. Poll it before large writes to stay within plan. See Account and the Usage & quota guide.

curl https://api.ranktracker.com/v1/account/usage \
-H "Authorization: tkn_usr_your_api_key_here"

Where to go next