Developer docs

API reference

One base URL, one key, JSON in and out. Every endpoint below is also an MCP tool with the same name.

openapi.json · Error codes · MCP server

Base URL

https://storelift.net/v1

Every path below is relative to it. Send GET for reads and POST only on /actions.

Authentication

Send the key in Authorization: Bearer <key> or in x-api-key. Create keys under Settings › API; each key carries scopes, an optional IP allow list, an optional app list and an optional monthly credit budget. A key that reaches its budget answers 402 key_budget_reached until the month turns; the other keys keep working.

Live keys (sl_live_) for the REST API are on Pro and above, or on any plan with a credit pack. Free includes one live key that works on the MCP server only. Test keys (sl_test_) return the sandbox data on every plan. Assistants sign in over OAuth through the MCP server on every plan.

Credits and limits

Each call costs the credits listed on it; /account and /usage cost 0, and a write's preview costs 0. Every response carries x-credits-used, x-credits-limit, x-credits-remaining and x-credits-reset.

Requests per second are set per plan; past it you get 429 rate_limited with retry-after. Hive endpoints open with purchased API credits or with Hive Explorer and also count against a daily allowance: 500 calls on pack credits, 10,000 with Hive Explorer. Credits per call

Sandbox

https://storelift.net/v1/sandbox answers every read below that has a sample response, with no key and no credits. The data is fixed and dated, so tests stay stable.

Test keys (sl_test_) return the same data on the live base URL and never write.

Reading a rank

A rank has three states and they stay distinct: measured: false means the query was not measured that day, rank: null means it was measured and the app is outside the results read, and a number is the rank.

Errors

Every error has the same body: { "error": "<code>", "message": "…", "docs": "…" }. error is stable; branch on it, not on message. Some codes add fields such as scope, field or limit. All error codes

Worked examples

Three jobs, from the first call to the result you keep. Every command runs against the sandbox, so it spends no credits and changes nothing, and each response is what the sandbox returns for it.

With your own key the paths are the same without /sandbox and each call costs the credits listed on it. A test key (sl_test_) answers with the sandbox data there too, at 0 credits.

Add this week's keyword ranks to a report

Read one app's keywords in one storefront and keep a single row. Run it weekly and the rows line up into a trend.

Scope
apps:read
Credits
2 · 0 in the sandbox or with a test key
Plan
Sandbox and test key: every plan · live key: Pro and above
Works from
API 1.5.0
  1. Find the app id.

    curl "https://storelift.net/v1/sandbox/apps"
    Response
  2. Read every tracked keyword of that app in the storefront.

    curl "https://storelift.net/v1/sandbox/apps/sbx-lumen/keywords?country=us"
    Response (keywords: 2 of 8 rows)

Fold the answer into the row you store. A keyword with measured: true and rank: null was measured and the app is absent from the results; it is counted apart from a keyword that was not measured.

function weeklyRow(body) {
  const ranked = body.keywords.filter((k) => k.measured && k.rank !== null);
  const best = ranked.reduce((a, b) => (a && a.rank <= b.rank ? a : b), null);
  return {
    measuredAt: body.measuredAt,
    country: body.country,
    platform: body.platform,
    keywords: body.keywords.length,
    ranked: ranked.length,
    absent: body.keywords.filter((k) => k.measured && k.rank === null).length,
    notMeasured: body.keywords.filter((k) => !k.measured).length,
    best: best && { term: best.term, rank: best.rank },
  };
}

Result

{
  "measuredAt": "2026-09-27T02:10:00.000Z",
  "country": "us",
  "platform": "ios",
  "keywords": 8,
  "ranked": 6,
  "absent": 2,
  "notMeasured": 0,
  "best": {
    "term": "nature sounds",
    "rank": 29
  }
}

An app id the workspace does not have answers 404:

curl "https://storelift.net/v1/sandbox/apps/sbx-unknown/keywords?country=us"
{
  "error": "not_found",
  "message": "No such app in this workspace.",
  "docs": "https://storelift.net/docs/api/errors#not_found"
}

Ask who ranks above you

The same data through the MCP server: the assistant makes two tool calls. To send them by hand, put a test key in STORELIFT_TEST_KEY.

Scope
apps:read
Credits
2 · 0 with a test key
Plan
Test key and OAuth: every plan · live key: every plan
Works from
API 1.5.0 · tools in storelift-mcp 1.1.1
  1. list_apps returns the app id.

    curl "https://storelift.net/mcp" \
      -H "Authorization: Bearer $STORELIFT_TEST_KEY" \
      -H "content-type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_apps","arguments":{}}}'
    Response
  2. get_rivals returns the apps ranking above it, keyword by keyword.

    curl "https://storelift.net/mcp" \
      -H "Authorization: Bearer $STORELIFT_TEST_KEY" \
      -H "content-type: application/json" \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_rivals","arguments":{"appId":"sbx-lumen","country":"us"}}}'
    Response (rivals: 2 of 3 rows)

What the answer says, rival by rival. ourRank: null is a keyword the app is absent from.

function rivalsAhead(body) {
  return body.rivals.map((r) => ({
    name: r.name,
    ratings: r.ratings,
    aheadOn: r.terms.length,
    absentOn: r.terms.filter((t) => t.ourRank === null).length,
  }));
}

Result

[
  {
    "name": "Calm Rain",
    "ratings": 57046,
    "aheadOn": 8,
    "absentOn": 2
  },
  {
    "name": "Noisli",
    "ratings": 16114,
    "aheadOn": 8,
    "absentOn": 2
  },
  {
    "name": "Deep Sleep Tones",
    "ratings": 64683,
    "aheadOn": 8,
    "absentOn": 2
  }
]

An app id the workspace does not have comes back as a tool error the assistant can read, not as a failed request:

curl "https://storelift.net/mcp" \
  -H "Authorization: Bearer $STORELIFT_TEST_KEY" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"get_rivals","arguments":{"appId":"sbx-unknown","country":"us"}}}'
ERROR: No such app in this workspace.

See what a credential can read, over HTTP or MCP

The first call of any integration: which plan, which scopes, which apps and how many credits are left. GET /account and the whoami tool return the same body, and neither costs credits.

Scope
none
Credits
0
Plan
Sandbox, test key and OAuth: every plan · live key: Pro and above
Works from
API 1.5.0 · tools in storelift-mcp 1.5.0
  1. Over HTTP.

    curl "https://storelift.net/v1/sandbox/account"
    Response
  2. The same answer as a tool call.

    curl "https://storelift.net/mcp" \
      -H "Authorization: Bearer $STORELIFT_TEST_KEY" \
      -H "content-type: application/json" \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'
    Response

Check the scope a call needs before you make it, and read the balance before a batch.

function allowance(body) {
  return {
    plan: body.plan,
    auth: body.auth,
    scopes: body.scopes,
    apps: body.apps.map((a) => a.id),
    creditsLeft: body.credits.limit - body.credits.used,
    ratePerSecond: body.ratePerSecond,
  };
}

Result

{
  "plan": "studio",
  "auth": "test_key",
  "scopes": [
    "apps:read",
    "reviews:read",
    "ai:read",
    "revenue:read",
    "hive:read",
    "hive:spy",
    "hive:estimate",
    "ads:read"
  ],
  "apps": [
    "sbx-lumen",
    "sbx-quill"
  ],
  "creditsLeft": 50000,
  "ratePerSecond": 10
}

Without credentials the MCP endpoint answers 401 with a JSON-RPC error, and a client that supports MCP authorization starts the OAuth sign-in from it:

curl "https://storelift.net/mcp" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'
{
  "jsonrpc": "2.0",
  "id": 9,
  "error": {
    "code": -32001,
    "message": "No credentials were sent. Sign in with OAuth on any plan, or add Authorization: Bearer <your Storelift API key>. Live keys require Pro, Studio, Enterprise, or an eligible Free account with purchased API credits or active Explorer. Tool access still depends on plan, scopes and credits. Generate a key at Storelift → Settings → API keys."
  }
}

Common parameters

These read the same on every endpoint that takes them; each endpoint lists the ones it takes.

ParameterInTypeDescription
appIdpathstringapp id from list_apps
countryquerystringcountry code, e.g. tr, us (defaults to the app's first country)
platformqueryios | androiddefaults to ios
countryquerystringstorefront code, e.g. us (defaults to us)

Account

The key's own plan, scopes and usage, the account search and the alert feed. Reading plan and usage costs 0 credits.

  • GET /alertsThe alert feed of this workspace — rank drops and rises, keywords entering or leaving the results, rivals…
  • GET /portfolioToday across every app in the workspace, one row per app: live users, users and new users, ad revenue,…
  • GET /accountWho this key or session belongs to: plan, how it authenticated, the scopes it carries, the apps it may…
  • GET /usageEvery quota of the workspace with what is used and the reset time: tracked keywords, apps, discovery, AI…
  • GET /searchSearch this account's own records — app names and store ids, tracked keywords, and the rival apps that…
  • GET /fetchThe full record behind one id from search: an app card, one tracked keyword with its current state and…

Your apps

The apps this workspace tracks: ranks, rivals, reviews, store page, listing timeline, featuring, charts and listing checks.

Revenue & Apple Ads

The connected sources per app: revenue, subscription events, the App Store funnel, prices and offer codes, the before/after of a change, and Apple Ads campaigns, keywords, search terms and commands. Scopes revenue:read and ads:read.

Hive

Any app or market the Storelift network measures, whether you track it or not. Part of Hive Explorer.

Writes

Every write runs in two steps: a preview that changes nothing, then a confirm with the token it returned.