DocumentationUse your data
API
Read and write your projects, keywords and positions from your own tools.
The API is included in every account, at no extra cost. Anything the interface does with your projects, keywords and positions can be done over HTTP.
Get a key
- 1Open Settings, then the Account tabThe API key section is at the bottom of the tab.
- 2Click GenerateThe key starts with
pt_live_. Copy it straight away: it is shown once, because we keep only a hash of it and its first characters. Treat it like a password. - 3Revoke or regenerate whenever you wantRegenerate replaces the existing key, Revoke deletes it. Either way the old key stops working immediately.
Authentication
Every request carries the key in the X-API-Key header. The base URL is https://tool.positiontiger.com/api.
curl https://tool.positiontiger.com/api/projects \
-H "X-API-Key: pt_live_your_key"Without a valid key the response is a 401 with {"error":"Unauthorized"}.
The resources
| Resource | What it does |
|---|---|
/projects | List, create, rename and delete your projects, add your sites to them or remove them. |
/sites | List the projects you can reach, including ones shared with you, create and delete them: here a "site" is a project. |
/keywords | List, add and delete keywords (by term, or one tracking row at a time), force a refresh, add or remove tags. |
/positions | Read the positions that were checked: this is the endpoint that feeds a dashboard. |
/competitors | List, add and remove competitors on a project. |
/tags | Create, rename, colour and delete a project's tags. |
Reading positions
GET /api/positions is the richest resource. Only the project is required: its name (project) or its id (projectId, from GET /api/projects). Wherever the API takes project, you can give projectId instead.
A project name is unique within your account. If older projects share a name, project always means the oldest of them when reading, and the response says so in meta (sameName lists the other ids, note explains). A write (adding, deleting, refreshing, tags, competitors) with such a name is refused (409, with the ids): use projectId then.
curl "https://tool.positiontiger.com/api/positions?project=Acme%20Shop&days=30&device=desktop&include=all" \
-H "X-API-Key: pt_live_your_key"| Parameter | Values | Default |
|---|---|---|
project | the project's name | required unless projectId |
projectId | the project's id, instead of its name | — |
days | 1 to 365 | 90 |
startDate, endDate | YYYY-MM-DD. endDate alone: the days before it | the days window |
device | desktop, mobile, all | all |
keywords | comma-separated list | all |
tags | comma-separated list. A tag the project does not have matches no keyword (an empty list) | all |
site | one of the project's sites (its exact domain, subdomains included): positions are measured for that site, over every keyword. Text matching several sites returns a 400 listing them | each keyword's tracked site, failing that the project's first own site |
minPosition, maxPosition | 1 to 60 | no filter |
hideUnranked | true to drop unranked keywords | false |
sortBy | position, volume, cpc, term, date, evolution | position |
order | asc or desc | asc for position |
limit, offset | limit up to 500 | 100 and 0 |
include | volume, cpc, history, all | no extras |
Each row returns the keyword, the position, the previous position, the change, the ranking URL, the date, the device, the location, the tracked site and the tags. meta.filtered gives the total, which is what you page through with offset.
Your site counts with all its subdomains (fr.example.com, blog.example.com), as in the dashboard: the position is the best of them, and url says which.
The position and date are those of the latest check, as in the dashboard and reports: a keyword checked while your site was not in the results returns "position": null, dated that check. lastRankedPosition and lastRankedDate give the last time your site ranked (null if it never has). previousPosition is the position at the previous check. With a past endDate, the position and date are those of the latest check up to that date, included.
{
"keywords": [
{
"term": "acme shop",
"position": 1,
"previousPosition": 2,
"evolution": 1,
"url": "https://acme-shop.com/",
"date": "2026-09-22T00:00:00Z",
"lastRankedPosition": 1,
"lastRankedDate": "2026-09-22T00:00:00Z",
"device": "desktop",
"location": "FR",
"site": "https://acme-shop.com",
"tags": ["brand"]
}
],
"meta": { "project": "Acme Shop", "projectId": 4746, "filtered": 381, "returned": 100, "limit": 100, "offset": 0 }
}Dates are YYYY-MM-DDT00:00:00Z (the day of the check, UTC). If positions cannot be read at that moment, the response is a 503 with a Retry-After header: try again a few minutes later (no position is ever returned empty by mistake).
Adding a keyword
curl -X POST https://tool.positiontiger.com/api/keywords \
-H "X-API-Key: pt_live_your_key" \
-H "Content-Type: application/json" \
-d '{"project":"Acme Shop","term":"cordless drill","location":"FR","lang":"fr","device":"desktop"}'A keyword the project already tracks (same term, country, language and device) returns a 409, and nothing is charged. The same term in another country or on the other device is another keyword. A keyword another of your projects already tracks (same term, country, language and device) is free only if you already paid for it today (UTC day): it shares that check. Otherwise it costs 6 pages and is checked right away. If your balance is empty the response is a 403 with the code INSUFFICIENT_CREDITS.
location is the country code (FR when omitted). A country we do not track positions in is refused: 400, with every country and its code listed in countries, and nothing is charged.
lang is the search language, the country's own when omitted. Any language is accepted with any country, as in the dashboard: positions are asked of Google in that country (gl) in that language (hl), which is what someone in that country with Google set to that language sees. When Google Ads has no volume for that language in that country, the volume of the country's languages is shown.
Projects
| Method | Path | What it does |
|---|---|---|
GET | /api/projects | Lists every project, with its keyword count, tracked sites and competitors. ?include=keywords adds the keywords. |
GET | /api/projects/{id} | Fetches one project by id. |
POST | /api/projects | Creates a project: name, optionally trackedSites and logo. |
PUT | /api/projects | Renames a project or adds sites to it: projectId (or name) and newName, logo, trackedSites. trackedSites adds the sites the project does not have yet: the ones not in the list stay, and a site already there (as a site or a competitor) is not added a second time. |
DELETE | /api/projects/sites | Removes one or several of your sites from a project: projectId (or project), url or urls. For a competitor, use DELETE /api/competitors. |
DELETE | /api/projects | Deletes a project and everything in it: projectId (or name). A name two projects share returns a 409 with their ids. |
DELETE /api/projects/sites recognises a site by its domain, with or without https://, whatever the path (www.example.com and https://www.example.com/en are the same site). It never removes the project's last own site: the request is then refused (409) and nothing is removed; add another site first. The project's keywords and their history stay. The response gives the number of sites removed (deleted) and the ones left (trackedSites).
Sites
In /api/sites, a "site" is a project: siteId is a project's id. To add or remove one of the sites tracked in a project, see PUT /api/projects and DELETE /api/projects/sites above.
| Method | Path | What it does |
|---|---|---|
GET | /api/sites | Lists the projects you can reach, including ones shared with you, with your role. The other resources read only your own projects: a shared project's id returns a 404 there. |
GET | /api/sites/{id} | Fetches one project by id. |
POST | /api/sites | Creates a project: name, optionally logo. |
DELETE | /api/sites | Deletes the whole project, with its keywords, sites and competitors: siteId, the project's id. To remove just one of your sites, use DELETE /api/projects/sites. |
Keywords
| Method | Path | What it does |
|---|---|---|
GET | /api/keywords | Lists your keywords with their location, language, device and tags. ?project= narrows to one project. |
GET | /api/keywords/{id} | Fetches one keyword by its tracking id — the one GET /api/keywords returns, not the keywordId. |
POST | /api/keywords | Adds a keyword: project, term, optionally location, lang, device. |
DELETE | /api/keywords | Deletes keywords by term: project and terms. Each term is deleted from the project in every country and on both devices. |
DELETE | /api/keywords/{id} | Deletes a single tracking row: one keyword, in one project, one country, on one device. id is the one GET /api/keywords returns, not the keywordId. A row that is not in one of your projects returns a 404. |
POST | /api/keywords/refresh | Queues keywords for a check straight away: project (or projectId), a keywords list, or "mode": "all" for every keyword of the account. |
POST | /api/keywords/tags | Applies a tag to a list of keywords: project, keywords, tag. |
DELETE | /api/keywords/tags | Removes that tag from the same keywords. |
Every keyword carries two separate identifiers, and they are not interchangeable. id is your tracking row: it belongs to your account and it is what GET and DELETE /api/keywords/{id} take. keywordId is the keyword itself — the term, language and country together — shared by every account tracking that term. The two run as separate sequences, so passing one where the other is expected addresses an unrelated keyword. GET /api/keywords returns both, for reference. Reading positions needs no identifier at all: GET /api/positions works from the project name and the terms.
A delete, by term or by id, removes only your tracking rows. A check already running when you delete is made, and counted; a check still waiting leaves the queue, unless the keyword is still tracked on that device.
POST /api/keywords/refresh must always say what to refresh: a project (project or projectId), a non-empty list of terms (keywords, across all your projects when none is given), or "mode": "all" for every keyword of the account, sent alone: the body is exactly {"mode": "all"}, and any other field beside it is refused (400). An empty body, {} or a misspelt field is refused (400): nothing is refreshed or charged. Every check is counted, 6 pages per keyword and device, even if the keyword was already checked today.
Positions
One resource, GET /api/positions, whose parameters do all the work. The combinations that earn their keep:
| Call | What it returns |
|---|---|
?project=Nom | Every position in the project over the last 90 days. |
&maxPosition=10 | The Top 10 only. |
&include=volume,cpc | Search volume and CPC on every row: your own figures where you corrected them on the dashboard, as it shows them. |
&include=history&days=30 | The history over the period: one point per day your site ranked (unranked days are left out). |
&device=mobile | Mobile positions only. |
&minPosition=1&maxPosition=50&hideUnranked=true | A slice of positions, with unranked keywords dropped. |
Competitors
| Method | Path | What it does |
|---|---|---|
GET | /api/competitors?project= | Lists a project's competitors. |
GET | /api/competitors/{id} | Fetches one competitor by id. |
POST | /api/competitors | Adds one or several competitors: project, url or urls. added counts the ones actually added: a site the project already has, as a competitor or as a site, is not added a second time. |
DELETE | /api/competitors | Removes one or several competitors from the project: project, url or urls. |
Tags
| Method | Path | What it does |
|---|---|---|
GET | /api/tags?project= | Lists a project's tags, with how many keywords carry each. |
GET | /api/tags/{id} | Fetches one tag by id. |
POST | /api/tags | Creates a tag: project, name, color. |
PUT | /api/tags | Renames a tag or changes its colour. |
DELETE | /api/tags | Deletes a tag: project, name or names. |
Try it safely
The dashboard's API Playground lists the endpoints, generates code in a dozen languages and shows a sample response. Requests run from the playground do not change your projects.
A question this does not answer? Write to us.