All articles
API & MCP

API endpoints

All endpoints of the JustRanks API: keyword search, adding keywords, search results of a day, keyword analysis, rank history, ASIN search, product page history, markets, signals and webhooks.

All addresses start with https://www.justranks.com/api/v1. Dates are YYYY-MM-DD; without from and to a period means the last 30 days. Visibility and all shares (coverage, top-10 share, deal share, top-3 share) are percentages from 0 to 100, so 97.03 means 97.03 %; position changes are places, prices are dollars. Where it says "counts", a keyword outside your markets uses the daily allowance (see Getting started with the API).

Keywords

GET /keywords?q=dog crate&limit=25 finds keywords in the database that contain a phrase, with their demand level. Does not count.

POST /keywords adds keywords that are not in the database yet, the same way as Add to the database in the app. Body: { "keywords": ["dog crate for large dogs"], "context": ["dog crate", "dog kennel"] } with up to 100 keywords; context is optional and names related keywords of the same search. The answer lists added, already_tracked, over_limit and invalid (not a search term, for example an ASIN or a link). The first capture starts right away; results usually appear within the hour. Your plan allows a set number of new keywords per 24 hours (see Getting started with the API); keywords that are already tracked do not count. meta.keyword_adds and the headers X-Quota-Keyword-Adds-Limit and X-Quota-Keyword-Adds-Remaining show what is left. Does not use the daily allowance.

GET /keywords/paused lists keywords you added that looked unrelated after their first capture (fewer than 10 products on page 1, or no product in common with the context keywords). They are paused and wait 14 days for you; then they are dropped. POST /keywords/paused with { "keywords": [...], "action": "track" } tracks them anyway, "action": "remove" removes them.

GET /keywords/{keyword}/serp?date= returns the organic search results of a keyword on one day: position, ASIN, family, title, image, price, list price, rating, reviews, "bought in past month", badges and delivery promise. brand is filled only when Amazon shows it on the search page, which is rare; the product page history has the brand of your own products and key competitors. Without date, the latest capture. Keywords captured every 48h may not have a capture on every day; then the capture up to three days earlier is used and capture_date names its day. Counts.

GET /keywords/{keyword}/history?from&to returns the position of every product on a keyword at each capture of the period. A capture is one day (daily keywords) or two days (keywords captured every 48h). Counts.

GET /keywords/{keyword}/related?limit=50 returns keywords whose page-1 products overlap with this keyword, with demand level. Counts the keyword you ask about.

Analysis

POST /analysis answers "who dominates these keywords". Body:

{ "keywords": ["dog crate", "dog kennel"], "from": "2026-10-01", "to": "2026-10-23", "compare": true, "own_asins": ["B0…"], "limit": 100 }

For each product (variants counted as one family) you get visibility, average position, coverage, top-10 share, JustRanks Score, volatility, deal share and position change, plus its latest price, rating and badges. market adds median price and deal share of the top 10, volatility, the share of the three most visible products and median reviews. With "compare": true, every number also shows its change against the period of the same length just before. 1 to 100 keywords per request; counts.

Products

GET /asins/{asin}/keywords?from&to is the ASIN search of the Explorer: the up to 100 most relevant keywords where the product ranked on page 1 in the period, with best position, last position and how often it was there. Variants count as one product; family=false looks at this ASIN only. analyze=true adds the analysis of these keywords (then the keywords count as well). Uses one ASIN search of the daily allowance.

GET /asins/{asin}/history?keywords=dog crate,dog kennel&from&to is the product drilldown of the Explorer: the product's position on each keyword at every capture, its visibility, average position, coverage, top-10 share and JustRanks Score over time, its price, rating and reviews over time, position changes, and which of its variants Amazon showed and how often. All keywords count equally. Without keywords, the most relevant keywords of the ASIN search are used (one ASIN search of the daily allowance). Keywords count.

GET /asins/{asin}/family lists all variants that count as one product, with title and image. Does not count.

GET /asins/{asin}/pdp?from&to returns the daily product page of one of your own products or key competitors: price, Buy Box seller and fulfilment, other sellers, availability, coupon, deal badge, delivery, Best Seller badge, sales rank, rating and one-star share, variant count, and a fingerprint of title, images, bullets, A+ content and videos that changes when the listing changes. Product pages are only captured for products in your markets; other ASINs answer 403 not_in_your_markets.

Markets

A market is a set of up to 100 keywords with your own products and up to 3 key competitors. Saving a market makes its keywords run daily and starts product page captures for its products. The limits are the same as in the app.

  • GET /markets: your markets.
  • POST /markets: create a market. Body: { "name": "Dog Crate", "keywords": ["dog crate", { "keyword": "dog kennel", "tier": "core" }], "own_asins": ["B0…"], "key_competitors": ["B0…"] }. Keywords that are not in the database yet are added and captured from the next night on. A keyword can carry a priority (core, important, standard, minor).
  • GET /markets/{id}: one market with its keywords, priorities, own products and key competitors.
  • PATCH /markets/{id}: change a market. Only the fields you send change; a list replaces the saved list. Keywords you send without a priority keep their saved priority.
  • DELETE /markets/{id}: delete a market.
  • GET /markets/{id}/analysis?from&to&compare=true: the analysis of the market, as on its market page, with your own products and key competitors marked. Does not count.

Signals

GET /signals?market=&asin=&codes=&sev=critical,high&limit=50&cursor= returns the signals of your markets, the same as on the Signals page: what happened, to which product, with the facts as numbers. ongoing lists open incidents such as a lost Buy Box. Use meta.next_cursor as cursor for the next page. Signal rules cannot be changed through the API; to build your own rules, use the search results and product page history.

Webhooks

Instead of asking for new signals, let JustRanks send them. New signals of your markets are sent in batches to your address, usually within minutes after they appear in the app.

  • POST /webhooks with { "url": "https://example.com/justranks", "severities": ["critical", "high"], "market_ids": [12] } creates a webhook. severities and market_ids are optional (default: all severities shown on the Signals page, all markets). The answer contains a secret. It is shown once.
  • GET /webhooks lists your webhooks with their status (active, failing, disabled) and the last error.
  • POST /webhooks/{id}/test sends a test event right away and tells you whether your server accepted it.
  • DELETE /webhooks/{id} deletes a webhook.

Each call is a POST with { "type": "signals", "webhook_id": 3, "sent_at": "…", "signals": [ … ] }. The signals have the same format as GET /signals. Answer with any 2xx status. If your server does not answer in time or answers with an error, JustRanks retries with growing pauses; after many failures in a row the webhook is switched off and shows as disabled. Up to 5 webhooks per account.

Every call carries X-JustRanks-Signature: t=<timestamp>,v1=<signature>. The signature is an HMAC-SHA256 of <timestamp>.<raw body> with your secret. Check it before you trust the call, and reject old timestamps:

const crypto = require('crypto')
function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300
  return fresh && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

Usage

GET /usage returns your plan, what you have used today and your limits.

Related