From 21e1e5a9010a1b4ed772c080b31f45979cdf7a11 Mon Sep 17 00:00:00 2001 From: Asolytics Date: Thu, 18 Jun 2026 21:26:00 +0300 Subject: [PATCH] Add new public endpoints and recommended-keywords pagination (v1.1.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sync the API reference snapshot and document three new endpoints plus a breaking change to recommended-keywords: - applications/ranking/latest-by-keywords — latest position per keyword - projects/countries-keywords-counts — per-country keyword counters - subscription/limits — plan total/used limits (new Subscription tag) - recommended-keywords is now paginated (page/per_page) with state/source filters Regenerate references/{openapi.json,endpoints.md} via sync_openapi.py, update SKILL.md / api-overview.md / README.md, and bump the skill version 1.0.1 -> 1.1.0 across VERSION, SKILL.md, plugin.json, marketplace.json. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01542mJMhSRzu5Wuv8DDiZZN --- .claude-plugin/marketplace.json | 4 +- .claude-plugin/plugin.json | 4 +- README.md | 6 +- skills/asolytics-api/SKILL.md | 15 +- skills/asolytics-api/VERSION | 2 +- .../asolytics-api/references/api-overview.md | 28 +- skills/asolytics-api/references/endpoints.md | 53 +- skills/asolytics-api/references/openapi.json | 810 +++++++++++++----- 8 files changed, 686 insertions(+), 236 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b7fd940..eb14ad8 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,8 +9,8 @@ { "name": "asolytics-api", "source": "./", - "description": "Use the Asolytics Public API for App Store Optimization (ASO) research and automation: app metadata, availability, versions, installs and revenue estimates, rankings, keyword metrics, live search, recommended keywords, keyword tracking, competitors, projects, balance, and store charts.", - "version": "1.0.1", + "description": "Use the Asolytics Public API for App Store Optimization (ASO) research and automation: app metadata, availability, versions, installs and revenue estimates, rankings, keyword metrics, live search, recommended keywords, keyword tracking, competitors, projects, per-country keyword counts, balance, subscription limits, and store charts.", + "version": "1.1.0", "author": { "name": "Asolytics", "url": "https://asolytics.pro" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 320df2f..db0552d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "asolytics-api", "displayName": "Asolytics API — App Store Optimization (ASO)", - "description": "Use the Asolytics Public API for App Store Optimization (ASO) research and automation: app metadata, availability, versions, installs and revenue estimates, rankings, keyword metrics, live search, recommended keywords, keyword tracking, competitors, projects, balance, and store charts.", - "version": "1.0.1", + "description": "Use the Asolytics Public API for App Store Optimization (ASO) research and automation: app metadata, availability, versions, installs and revenue estimates, rankings, keyword metrics, live search, recommended keywords, keyword tracking, competitors, projects, per-country keyword counts, balance, subscription limits, and store charts.", + "version": "1.1.0", "author": { "name": "Asolytics", "url": "https://asolytics.pro" diff --git a/README.md b/README.md index fe427ad..4ddbc08 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,14 @@ This is mainly an **ASO research & intelligence API** — pull keyword and app d | --- | --- | | **Keyword metrics** | Popularity (search volume), results count, and other metrics for any keyword — plus **popularity history** over time and an app's **ranking history** for a keyword. | | **Live search results** | The real **top-50 search results** for any keyword, country, and store — fetched live, right now. | -| **Keyword ideas** | Recommended keywords Asolytics suggests for an app. | +| **Keyword ideas** | Recommended keywords Asolytics suggests for a project — filterable by state and source. | | **App metadata** | Current store listing for any app — title, subtitle, description, screenshots & media, version history, and the countries it's available in. | | **Installs & revenue estimates** | Monthly **estimated installs** and **revenue**, per app and per country. | -| **Rankings & store charts** | An app's category and keyword **ranking history**, plus full **top-500 store charts** by country, category, and device. | +| **Rankings & store charts** | An app's category and keyword **ranking history** (or the **latest position per keyword** in one call), plus full **top-500 store charts** by country, category, and device. | | **Competitors** | The competitor set for any app. | | **Lookups** | Reference data to build valid queries: countries, locales, devices, categories, and clusters. | -It can also **manage your own Asolytics workspace** in one place — your projects, tracked keywords & folders, competitor marks, and your remaining API **token balance** (the API is metered). +It can also **manage your own Asolytics workspace** in one place — your projects (including **per-country keyword counts**), tracked keywords & folders, competitor marks, your **plan limits** (`total` vs `used`), and your remaining API **token balance** (the API is metered). Full endpoint-by-endpoint reference: [`skills/asolytics-api/references/endpoints.md`](skills/asolytics-api/references/endpoints.md). diff --git a/skills/asolytics-api/SKILL.md b/skills/asolytics-api/SKILL.md index 4c0be9a..312b43a 100644 --- a/skills/asolytics-api/SKILL.md +++ b/skills/asolytics-api/SKILL.md @@ -1,7 +1,7 @@ --- name: asolytics-api -version: 1.0.1 -description: Use the Asolytics Public API for ASO research and automation. Trigger this skill when the agent needs to query app metadata, availability, versions, installs, revenue, rankings, keyword metrics, live search results, recommended keywords, tracked keywords, tracking folders, competitors, projects, balance, or store charts from Asolytics. Also use it when a user wants curl examples, lightweight integrations, or repeatable reporting workflows against the Asolytics API. +version: 1.1.0 +description: Use the Asolytics Public API for ASO research and automation. Trigger this skill when the agent needs to query app metadata, availability, versions, installs, revenue, rankings, keyword metrics, live search results, recommended keywords, tracked keywords, tracking folders, competitors, projects, per-country keyword counts, balance, subscription/plan limits, or store charts from Asolytics. Also use it when a user wants curl examples, lightweight integrations, or repeatable reporting workflows against the Asolytics API. homepage: https://github.com/Asolytics-Pro/asolytics-app-store-optimization-api --- @@ -63,7 +63,7 @@ Use the Applications endpoints for: - store availability - version history - installs and revenue history -- ranking history +- ranking history (full history, or the latest known position per keyword via `applications/ranking/latest-by-keywords`) Start with the narrowest possible app set because some metadata endpoints bill per returned pair. @@ -75,7 +75,7 @@ Use the Keywords and Recommended Keywords endpoints for: - popularity history - latest keyword metrics - live search top 50 -- recommended keyword review +- recommended keyword review — `recommended-keywords` is **paginated** (`page`/`per_page`, 100–1000); loop until `pagination.current_page == pagination.total_pages`, and narrow with `filters[recommended_keyword_state][]` (recommended/tracked/declined) or `filters[sources][]` (meta/competitors/suggestions/rankings/related) - async rank tracking jobs with optional webhook callbacks When the user wants automation, prefer plain HTTP calls and return the exact request payload you used. @@ -101,6 +101,13 @@ Use Competitors and Store Charts for: - marking or unmarking a competitor - retrieving top-500 charts by store, country, cluster, category, and device +### Project & Account Insight + +Use Projects and Subscription for cheap, high-level dashboards: + +- `projects/countries-keywords-counts` — per-country tracked / recommended / ranking keyword counts plus their day-over-day `dynamic`, for one project. A cheap snapshot of project breadth; note it reflects the **previous day** (use the list endpoints for exact, current values). +- `subscription/limits` — plan `total` vs `used` for keywords, apps, archived apps, and public API tokens. Pair with `GET /public-api/v1/balance` whenever quota or token headroom matters before a wide query. + ## Guardrails - Treat the API as unstable unless the current OpenAPI spec confirms the shape you expect. diff --git a/skills/asolytics-api/VERSION b/skills/asolytics-api/VERSION index 7dea76e..9084fa2 100644 --- a/skills/asolytics-api/VERSION +++ b/skills/asolytics-api/VERSION @@ -1 +1 @@ -1.0.1 +1.1.0 diff --git a/skills/asolytics-api/references/api-overview.md b/skills/asolytics-api/references/api-overview.md index 92c3bde..4a3febc 100644 --- a/skills/asolytics-api/references/api-overview.md +++ b/skills/asolytics-api/references/api-overview.md @@ -42,6 +42,7 @@ curl -sS \ - Installs history - Revenue history - Ranking history +- Latest ranking position per keyword (`applications/ranking/latest-by-keywords`) ### Common Catalogs @@ -60,7 +61,7 @@ Use these first when the user does not already know valid store, locale, country - Latest metrics - Live search top 50 - Ranking track jobs -- Recommended keywords +- Recommended keywords (paginated; filter by state and source) - Decline and undecline actions ### Tracking @@ -75,10 +76,15 @@ Use these first when the user does not already know valid store, locale, country - Mark competitor - Unmark competitor +### Subscription + +- Plan limits — `total` vs `used` for keywords, apps, archived apps, and public API tokens (`subscription/limits`) + ### Other - Balance - Projects list +- Per-country keyword counters (`projects/countries-keywords-counts`) - Store charts ## Practical Workflow @@ -122,3 +128,23 @@ curl -sS \ -X POST "https://app.asolytics.pro/public-api/v1/tracking/folders" \ -d '{"project_id":123,"name":"Core Keywords","description":"Tier 1 terms"}' ``` + +### Latest position per keyword + +```bash +curl -sS \ + -H "X-PUBLIC-API-TOKEN: $ASOLYTICS_PUBLIC_API_TOKEN" \ + --get "https://app.asolytics.pro/public-api/v1/applications/ranking/latest-by-keywords" \ + --data-urlencode "origin_id=1234567890" \ + --data-urlencode "country_code=US" \ + --data-urlencode "keywords[]=fitness app" \ + --data-urlencode "keywords[]=workout tracker" +``` + +### Plan limits (total vs used) + +```bash +curl -sS \ + -H "X-PUBLIC-API-TOKEN: $ASOLYTICS_PUBLIC_API_TOKEN" \ + "https://app.asolytics.pro/public-api/v1/subscription/limits" +``` diff --git a/skills/asolytics-api/references/endpoints.md b/skills/asolytics-api/references/endpoints.md index 15809cc..039f1d4 100644 --- a/skills/asolytics-api/references/endpoints.md +++ b/skills/asolytics-api/references/endpoints.md @@ -3,7 +3,7 @@ - Source docs: `https://app.asolytics.pro/api/public-api/documentation` - Source OpenAPI: `https://app.asolytics.pro/api/public-api/docs?public-api-docs.json` - Generated from spec version: `1.0.0-alpha` -- Generated at: `2026-06-12T15:44:35.307158+00:00` +- Generated at: `2026-06-18T18:24:01.072654+00:00` ## Authentication @@ -64,6 +64,16 @@ `filters[position][from]` - in `query` - integer `filters[position][to]` - in `query` - integer +### `GET /public-api/v1/applications/ranking/latest-by-keywords` + +- Summary: Latest ranking position per keyword +- Notes: Returns the app's **latest known** ranking position for each of the requested keywords in the given store/country. Because keywords are parsed on independent schedules, each keyword carries its own `date` — they will not always share the same parse day. Keywords the app does not currently rank for are omitted from the response. The store (Google Play / App Store) is inferred from the `origin_id`. **Costs:** 1 token per 30 requested keywords, minimum 5 per call (billed on the request, not the number of rows returned). +- Parameters: + +`origin_id` - in `query` - string - required +`country_code` - in `query` - string - required +`keywords[]` - in `query` - array - required - maxItems=1000 + ### `GET /public-api/v1/applications/revenue` - Summary: Application revenue history @@ -140,7 +150,7 @@ - Parameters: `project_id` - in `query` - integer - required -`filters[competitor_state][]` - in `query` - array +`filters[competitor_state][]` - in `query` - array - maxItems=50 ### `POST /public-api/v1/competitors/mark` @@ -176,7 +186,7 @@ `keywords[]` - in `query` - array - required - maxItems=250 `store` - in `query` - string - required - enum: APP_STORE, GOOGLE_PLAY `country_code` - in `query` - string - required -`metrics[]` - in `query` - array - required +`metrics[]` - in `query` - array - required - maxItems=50 ### `GET /public-api/v1/keywords/popularity` @@ -202,20 +212,6 @@ `date_from` - in `query` - string - required `date_to` - in `query` - string - required -### `POST /public-api/v1/keywords/ranking/track` - -- Summary: Track keyword rankings -- Notes: Runs an on-demand keyword-ranking scan for a (store, country) and returns each keyword's top positions with the apps holding them. Use this when you need fresh ranking data *now* for keywords you don't already have tracked. Without `response_conf` the call blocks on the scan and the response carries the result; with `response_conf.url` the task is queued and the result is later POSTed to that URL (the immediate response then only carries task info). Up to 500 keywords per request. Keywords already in your tracked-keyword quota are billed as free; the remainder are **paid keywords**. **Costs:** 5 tokens per paid keyword, minimum 5 per call. - -- Request body: - -- Content-Type: `application/json` -- `store` required: string (APP_STORE, GOOGLE_PLAY) -- `country_code` required: string — Sourced from `/v1/common-catalogs/countries`. -- `keywords` required: array -- `custom_data`: object — Arbitrary key/value data echoed back in the task info. -- `response_conf`: object — When present, the task is processed asynchronously and the result is POSTed to `url`. - ## Live Search ### `GET /public-api/v1/live-search` @@ -231,6 +227,14 @@ ## Projects +### `GET /public-api/v1/projects/countries-keywords-counts` + +- Summary: Get per-country keyword counters +- Notes: Returns, for every store country of the project, the tracked / recommended / ranking keyword counters together with their day-over-day `dynamic`. The result is keyed by 2-letter country code. **Note:** these counters are pre-aggregated and reflect the **previous day's** snapshot, so they may lag the real data by up to a day. For exact, up-to-the-moment values use the list endpoints directly (e.g. `/v1/tracking/keywords`, `/v1/recommended-keywords`, `/v1/applications/ranking`). **Costs:** 20 tokens per call. +- Parameters: + +`project_id` - in `query` - integer - required + ### `GET /public-api/v1/projects/list` - Summary: List user projects @@ -241,13 +245,15 @@ ### `GET /public-api/v1/recommended-keywords` - Summary: List recommended keywords -- Notes: Returns the keywords we recommend you track for a project in the given country, surfaced from the project's app metadata, competitors' metadata, store suggestions, current rankings and related keywords. Each entry carries the phrase, the `sources` that proposed it and its current `state` (`recommended` — fresh suggestion, `tracked` — already in your tracking set, `declined` — you previously dismissed it via `/decline`). Filter by state or by source(s) to narrow the working set. **Costs:** `max(10, ceil(returned / 100))` tokens — minimum 10 per call, then 1 extra token for every started bucket of 100 returned keywords. +- Notes: Returns the keywords we recommend you track for a project in the given country, surfaced from the project's app metadata, competitors' metadata, store suggestions, current rankings and related keywords. Each entry carries the phrase, the `sources` that proposed it and its current `state` (`recommended` — fresh suggestion, `tracked` — already in your tracking set, `declined` — you previously dismissed it via `/decline`). Filter by state or by source(s) to narrow the working set. The result is paginated — `pagination.total` reflects the filtered universe. **Costs:** `max(10, ceil(returned / 100))` tokens — minimum 10 per call, then 1 extra token for every started bucket of 100 keywords returned on the page. - Parameters: `project_id` - in `query` - integer - required `country_code` - in `query` - string - required -`filters[recommended_keyword_state][]` - in `query` - array -`filters[sources][]` - in `query` - array +`page` - in `query` - integer +`per_page` - in `query` - integer +`filters[recommended_keyword_state][]` - in `query` - array - maxItems=50 +`filters[sources][]` - in `query` - array - maxItems=50 ### `POST /public-api/v1/recommended-keywords/decline` @@ -289,6 +295,13 @@ `date_from` - in `query` - string - required `date_to` - in `query` - string - required +## Subscription + +### `GET /public-api/v1/subscription/limits` + +- Summary: Get subscription limits +- Notes: Returns the plan limits of the authenticated user's current subscription — `max_keywords`, `max_apps`, `max_archived_apps` and `max_public_api_tokens` — each as a `total` (plan allowance) / `used` (currently consumed) pair. Team members see their team owner's plan. **Costs:** 1 token per call. + ## Tracking Folders ### `GET /public-api/v1/tracking/folders` diff --git a/skills/asolytics-api/references/openapi.json b/skills/asolytics-api/references/openapi.json index 3db320e..90cd8ff 100644 --- a/skills/asolytics-api/references/openapi.json +++ b/skills/asolytics-api/references/openapi.json @@ -29,7 +29,8 @@ "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 128 }, "maxItems": 10 }, @@ -46,7 +47,8 @@ "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 35 }, "maxItems": 10 }, @@ -154,7 +156,8 @@ "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 128 }, "maxItems": 10 }, @@ -171,7 +174,8 @@ "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 35 }, "maxItems": 10 }, @@ -279,7 +283,8 @@ "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 128 }, "maxItems": 10 }, @@ -385,7 +390,8 @@ "description": "Store origin id of the application — App Store: numeric track id (e.g. `1234567890`); Google Play: bundle id (e.g. `com.acme.app`).", "required": true, "schema": { - "type": "string" + "type": "string", + "maxLength": 128 }, "example": "1234567890" } @@ -496,7 +502,8 @@ "description": "Store origin id of the application — App Store: numeric track id (e.g. `1234567890`); Google Play: bundle id (e.g. `com.acme.app`).", "required": true, "schema": { - "type": "string" + "type": "string", + "maxLength": 128 }, "example": "1234567890" }, @@ -627,7 +634,8 @@ "description": "Store origin id of the application — App Store: numeric track id (e.g. `1234567890`); Google Play: bundle id (e.g. `com.acme.app`).", "required": true, "schema": { - "type": "string" + "type": "string", + "maxLength": 128 }, "example": "1234567890" }, @@ -758,7 +766,8 @@ "description": "Store origin id of the application — App Store: numeric track id (e.g. `1234567890`); Google Play: bundle id (e.g. `com.acme.app`).", "required": true, "schema": { - "type": "string" + "type": "string", + "maxLength": 128 }, "example": "1234567890" }, @@ -855,6 +864,7 @@ "required": false, "schema": { "type": "integer", + "maximum": 500, "minimum": 1 }, "example": 1 @@ -866,6 +876,7 @@ "required": false, "schema": { "type": "integer", + "maximum": 500, "minimum": 1 }, "example": 10 @@ -962,6 +973,148 @@ ] } }, + "/public-api/v1/applications/ranking/latest-by-keywords": { + "get": { + "tags": [ + "Applications" + ], + "summary": "Latest ranking position per keyword", + "description": "Returns the app's **latest known** ranking position for each of the requested keywords in the given store/country. Because keywords are parsed on independent schedules, each keyword carries its own `date` — they will not always share the same parse day. Keywords the app does not currently rank for are omitted from the response. The store (Google Play / App Store) is inferred from the `origin_id`.

**Costs:** 1 token per 30 requested keywords, minimum 5 per call (billed on the request, not the number of rows returned).", + "operationId": "publicApiApplicationsRankingLatestByKeywords", + "parameters": [ + { + "name": "origin_id", + "in": "query", + "description": "App store origin id — App Store: numeric track id (e.g. `1234567890`); Google Play: bundle id (e.g. `com.acme.app`).", + "required": true, + "schema": { + "type": "string", + "maxLength": 128 + }, + "example": "1234567890" + }, + { + "name": "country_code", + "in": "query", + "description": "Sourced from `/v1/common-catalogs/countries`.", + "required": true, + "schema": { + "type": "string", + "maxLength": 2, + "minLength": 2 + }, + "example": "US" + }, + { + "name": "keywords[]", + "in": "query", + "description": "Keywords to look up (max 1000, each ≤80 chars).", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "maxLength": 80 + }, + "maxItems": 1000 + }, + "example": [ + "fitness app", + "workout tracker" + ] + } + ], + "responses": { + "200": { + "description": "Latest position per keyword.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/ApplicationsRankingLatestByKeywordsViewSchema" + } + }, + "type": "object" + } + ] + } + } + } + }, + "401": { + "description": "Missing or invalid public API token." + }, + "422": { + "description": "Validation error." + }, + "404": { + "description": "Application not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiAppNotFoundSchema" + } + } + } + }, + "402": { + "description": "Not enough tokens on the balance.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/PublicApiNotEnoughTokensSchema" + } + }, + "type": "object" + } + ] + } + } + } + }, + "500": { + "description": "Internal error.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/PublicApiInternalErrorSchema" + } + }, + "type": "object" + } + ] + } + } + } + } + }, + "security": [ + { + "publicApiToken": [] + } + ] + } + }, "/public-api/v1/balance": { "get": { "tags": [ @@ -1616,7 +1769,8 @@ "non_competitor", "indirect_competitor" ] - } + }, + "maxItems": 50 } } ], @@ -1735,6 +1889,7 @@ "competitor_origin_id": { "description": "Store origin id of the competitor app — App Store: numeric track id; Google Play: bundle id.", "type": "string", + "maxLength": 128, "example": "1234567890" }, "state": { @@ -1873,6 +2028,7 @@ "competitor_origin_id": { "description": "Store origin id of the competitor app — App Store: numeric track id; Google Play: bundle id.", "type": "string", + "maxLength": 128, "example": "1234567890" } }, @@ -1976,175 +2132,6 @@ ] } }, - "/public-api/v1/keywords/ranking/track": { - "post": { - "tags": [ - "Keywords" - ], - "summary": "Track keyword rankings", - "description": "Runs an on-demand keyword-ranking scan for a (store, country) and returns each keyword's top positions with the apps holding them. Use this when you need fresh ranking data *now* for keywords you don't already have tracked. Without `response_conf` the call blocks on the scan and the response carries the result; with `response_conf.url` the task is queued and the result is later POSTed to that URL (the immediate response then only carries task info). Up to 500 keywords per request. Keywords already in your tracked-keyword quota are billed as free; the remainder are **paid keywords**.

**Costs:** 5 tokens per paid keyword, minimum 5 per call.", - "operationId": "publicApiTrackKeywordsRanking", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "required": [ - "store", - "country_code", - "keywords" - ], - "properties": { - "store": { - "type": "string", - "enum": [ - "APP_STORE", - "GOOGLE_PLAY" - ], - "example": "APP_STORE" - }, - "country_code": { - "description": "Sourced from `/v1/common-catalogs/countries`.", - "type": "string", - "maxLength": 2, - "minLength": 2, - "example": "US" - }, - "keywords": { - "type": "array", - "items": { - "type": "string", - "example": "photo editor" - }, - "maxItems": 500, - "minItems": 1 - }, - "custom_data": { - "description": "Arbitrary key/value data echoed back in the task info.", - "type": "object", - "example": { - "client_request_id": "abc-123", - "campaign": "spring-2026" - }, - "nullable": true, - "additionalProperties": true - }, - "response_conf": { - "description": "When present, the task is processed asynchronously and the result is POSTed to `url`.", - "properties": { - "url": { - "type": "string", - "format": "uri", - "example": "https://example.com/webhook" - }, - "headers": { - "description": "Additional headers sent with the callback request. Values are strings.", - "type": "object", - "example": { - "Authorization": "Bearer xxx", - "X-Trace-Id": "abc" - }, - "nullable": true, - "additionalProperties": true - } - }, - "type": "object", - "nullable": true - } - }, - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "Synchronous result, or task info when processed asynchronously.", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/PublicApiResponseViewSchema" - }, - { - "properties": { - "data": { - "oneOf": [ - { - "$ref": "#/components/schemas/TrackKeywordsRankingResultViewSchema" - }, - { - "$ref": "#/components/schemas/TrackKeywordsRankingTaskInfoViewSchema" - } - ] - } - }, - "type": "object" - } - ] - } - } - } - }, - "401": { - "description": "Missing or invalid public API token." - }, - "422": { - "description": "Validation error." - }, - "402": { - "description": "Not enough tokens on the balance.", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/PublicApiResponseViewSchema" - }, - { - "properties": { - "data": { - "$ref": "#/components/schemas/PublicApiNotEnoughTokensSchema" - } - }, - "type": "object" - } - ] - } - } - } - }, - "500": { - "description": "Internal error.", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/PublicApiResponseViewSchema" - }, - { - "properties": { - "data": { - "$ref": "#/components/schemas/PublicApiInternalErrorSchema" - } - }, - "type": "object" - } - ] - } - } - } - } - }, - "security": [ - { - "publicApiToken": [] - } - ] - } - }, "/public-api/v1/keywords/ranking": { "get": { "tags": [ @@ -2157,12 +2144,13 @@ { "name": "keywords[]", "in": "query", - "description": "Keyword phrases (max 500).", + "description": "Keyword phrases (max 500, each ≤80 chars).", "required": true, "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 80 }, "maxItems": 500 }, @@ -2311,12 +2299,13 @@ { "name": "keywords[]", "in": "query", - "description": "Keyword phrases (max 1000).", + "description": "Keyword phrases (max 1000, each ≤80 chars).", "required": true, "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 80 }, "maxItems": 1000 }, @@ -2465,12 +2454,13 @@ { "name": "keywords[]", "in": "query", - "description": "Keyword phrases (max 250). Duplicates are deduplicated.", + "description": "Keyword phrases (max 250, each ≤80 chars). Duplicates are deduplicated.", "required": true, "schema": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 80 }, "maxItems": 250, "minItems": 1 @@ -2508,7 +2498,7 @@ { "name": "metrics[]", "in": "query", - "description": "Metrics to include in the response (min 1). Allowed values: `popularity`, `max_popularity`, `impressions`, `difficulty`, `language`.", + "description": "Metrics to include in the response (min 1, max 50). Allowed values: `popularity`, `max_popularity`, `impressions`, `difficulty`, `language`.", "required": true, "schema": { "type": "array", @@ -2522,6 +2512,7 @@ "language" ] }, + "maxItems": 50, "minItems": 1 }, "example": [ @@ -2638,7 +2629,8 @@ "in": "query", "required": true, "schema": { - "type": "string" + "type": "string", + "maxLength": 80 }, "example": "photo editor" }, @@ -2672,7 +2664,96 @@ ], "responses": { "200": { - "description": "Top-50 organic results.", + "description": "Top-50 organic results.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/PublicApiLiveSearchViewSchema" + } + }, + "type": "object" + } + ] + } + } + } + }, + "401": { + "description": "Missing or invalid public API token." + }, + "422": { + "description": "Validation error." + }, + "402": { + "description": "Not enough tokens on the balance.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/PublicApiNotEnoughTokensSchema" + } + }, + "type": "object" + } + ] + } + } + } + }, + "500": { + "description": "Internal error.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/PublicApiInternalErrorSchema" + } + }, + "type": "object" + } + ] + } + } + } + } + }, + "security": [ + { + "publicApiToken": [] + } + ] + } + }, + "/public-api/v1/projects/list": { + "get": { + "tags": [ + "Projects" + ], + "summary": "List user projects", + "description": "Returns every project (app + country) belonging to the authenticated user. The `id` of each project is what every project-scoped endpoint (tracking, recommended-keywords, competitors) expects as `project_id`.

**Costs:** 10 tokens per call.", + "operationId": "publicApiGetProjectsList", + "responses": { + "200": { + "description": "List of projects.", "content": { "application/json": { "schema": { @@ -2683,7 +2764,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/PublicApiLiveSearchViewSchema" + "$ref": "#/components/schemas/GetProjectsListResultViewSchema" } }, "type": "object" @@ -2696,9 +2777,6 @@ "401": { "description": "Missing or invalid public API token." }, - "422": { - "description": "Validation error." - }, "402": { "description": "Not enough tokens on the balance.", "content": { @@ -2751,17 +2829,29 @@ ] } }, - "/public-api/v1/projects/list": { + "/public-api/v1/projects/countries-keywords-counts": { "get": { "tags": [ "Projects" ], - "summary": "List user projects", - "description": "Returns every project (app + country) belonging to the authenticated user. The `id` of each project is what every project-scoped endpoint (tracking, recommended-keywords, competitors) expects as `project_id`.

**Costs:** 10 tokens per call.", - "operationId": "publicApiGetProjectsList", + "summary": "Get per-country keyword counters", + "description": "Returns, for every store country of the project, the tracked / recommended / ranking keyword counters together with their day-over-day `dynamic`. The result is keyed by 2-letter country code.

**Note:** these counters are pre-aggregated and reflect the **previous day's** snapshot, so they may lag the real data by up to a day. For exact, up-to-the-moment values use the list endpoints directly (e.g. `/v1/tracking/keywords`, `/v1/recommended-keywords`, `/v1/applications/ranking`).

**Costs:** 20 tokens per call.", + "operationId": "publicApiGetCountriesKeywordsCounts", + "parameters": [ + { + "name": "project_id", + "in": "query", + "description": "Sourced from `/v1/projects/list`.", + "required": true, + "schema": { + "type": "integer" + }, + "example": 123 + } + ], "responses": { "200": { - "description": "List of projects.", + "description": "Per-country keyword counters.", "content": { "application/json": { "schema": { @@ -2772,7 +2862,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/GetProjectsListResultViewSchema" + "$ref": "#/components/schemas/CountriesKeywordsCountsViewSchema" } }, "type": "object" @@ -2785,6 +2875,16 @@ "401": { "description": "Missing or invalid public API token." }, + "404": { + "description": "Project not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicApiProjectNotFoundSchema" + } + } + } + }, "402": { "description": "Not enough tokens on the balance.", "content": { @@ -2843,7 +2943,7 @@ "Recommended Keywords" ], "summary": "List recommended keywords", - "description": "Returns the keywords we recommend you track for a project in the given country, surfaced from the project's app metadata, competitors' metadata, store suggestions, current rankings and related keywords. Each entry carries the phrase, the `sources` that proposed it and its current `state` (`recommended` — fresh suggestion, `tracked` — already in your tracking set, `declined` — you previously dismissed it via `/decline`). Filter by state or by source(s) to narrow the working set.

**Costs:** `max(10, ceil(returned / 100))` tokens — minimum 10 per call, then 1 extra token for every started bucket of 100 returned keywords.", + "description": "Returns the keywords we recommend you track for a project in the given country, surfaced from the project's app metadata, competitors' metadata, store suggestions, current rankings and related keywords. Each entry carries the phrase, the `sources` that proposed it and its current `state` (`recommended` — fresh suggestion, `tracked` — already in your tracking set, `declined` — you previously dismissed it via `/decline`). Filter by state or by source(s) to narrow the working set. The result is paginated — `pagination.total` reflects the filtered universe.

**Costs:** `max(10, ceil(returned / 100))` tokens — minimum 10 per call, then 1 extra token for every started bucket of 100 keywords returned on the page.", "operationId": "publicApiListRecommendedKeywords", "parameters": [ { @@ -2868,6 +2968,30 @@ }, "example": "US" }, + { + "name": "page", + "in": "query", + "description": "Page number (default 1).", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "example": 1 + }, + { + "name": "per_page", + "in": "query", + "description": "Items per page (min 100, max 1000, default 100).", + "required": false, + "schema": { + "type": "integer", + "default": 100, + "maximum": 1000, + "minimum": 100 + }, + "example": 100 + }, { "name": "filters[recommended_keyword_state][]", "in": "query", @@ -2882,7 +3006,8 @@ "tracked", "declined" ] - } + }, + "maxItems": 50 } }, { @@ -2901,7 +3026,8 @@ "rankings", "related" ] - } + }, + "maxItems": 50 } } ], @@ -3028,8 +3154,10 @@ "type": "array", "items": { "type": "string", + "maxLength": 80, "example": "photo editor" }, + "maxItems": 1000, "minItems": 1 } }, @@ -3161,8 +3289,10 @@ "type": "array", "items": { "type": "string", + "maxLength": 80, "example": "photo editor" }, + "maxItems": 1000, "minItems": 1 } }, @@ -3299,7 +3429,8 @@ "description": "Cluster key from `/v1/common-catalogs/clusters`.", "required": true, "schema": { - "type": "string" + "type": "string", + "maxLength": 128 }, "example": "top-free" }, @@ -3309,7 +3440,8 @@ "description": "Category key from `/v1/common-catalogs/categories`.", "required": true, "schema": { - "type": "string" + "type": "string", + "maxLength": 128 }, "example": "36" }, @@ -3431,6 +3563,92 @@ ] } }, + "/public-api/v1/subscription/limits": { + "get": { + "tags": [ + "Subscription" + ], + "summary": "Get subscription limits", + "description": "Returns the plan limits of the authenticated user's current subscription — `max_keywords`, `max_apps`, `max_archived_apps` and `max_public_api_tokens` — each as a `total` (plan allowance) / `used` (currently consumed) pair. Team members see their team owner's plan.

**Costs:** 1 token per call.", + "operationId": "publicApiGetSubscriptionLimits", + "responses": { + "200": { + "description": "Current subscription limits.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/SubscriptionLimitsViewSchema" + } + }, + "type": "object" + } + ] + } + } + } + }, + "401": { + "description": "Missing or invalid public API token." + }, + "402": { + "description": "Not enough tokens on the balance.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/PublicApiNotEnoughTokensSchema" + } + }, + "type": "object" + } + ] + } + } + } + }, + "500": { + "description": "Internal error.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PublicApiResponseViewSchema" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/PublicApiInternalErrorSchema" + } + }, + "type": "object" + } + ] + } + } + } + } + }, + "security": [ + { + "publicApiToken": [] + } + ] + } + }, "/public-api/v1/tracking/keywords": { "get": { "tags": [ @@ -3584,8 +3802,10 @@ "type": "array", "items": { "type": "string", + "maxLength": 80, "example": "photo editor" }, + "maxItems": 1000, "minItems": 1 } }, @@ -3728,6 +3948,7 @@ "type": "integer", "example": 456 }, + "maxItems": 1000, "minItems": 1 } }, @@ -3955,10 +4176,12 @@ }, "name": { "type": "string", + "maxLength": 255, "example": "My folder" }, "description": { "type": "string", + "maxLength": 1000, "example": "Optional description", "nullable": true } @@ -4190,10 +4413,12 @@ "properties": { "name": { "type": "string", + "maxLength": 255, "example": "Renamed folder" }, "description": { "type": "string", + "maxLength": 1000, "example": "New description", "nullable": true } @@ -4338,8 +4563,10 @@ "type": "array", "items": { "type": "string", + "maxLength": 80, "example": "photo editor" }, + "maxItems": 1000, "minItems": 1 } }, @@ -4473,8 +4700,10 @@ "type": "array", "items": { "type": "string", + "maxLength": 80, "example": "photo editor" }, + "maxItems": 1000, "minItems": 1 } }, @@ -4965,6 +5194,34 @@ }, "type": "object" }, + "ApplicationsRankingLatestByKeywordsViewSchema": { + "description": "Map keyed by keyword. Each entry holds the app's latest known position for that keyword and the date that position was parsed. Dates may differ between keywords. Keywords the app does not currently rank for are omitted.", + "type": "object", + "example": { + "fitness app": { + "date": "2025-12-15", + "position": 1 + }, + "workout tracker": { + "date": "2025-12-14", + "position": 7 + } + }, + "additionalProperties": { + "properties": { + "date": { + "type": "string", + "format": "date", + "example": "2025-12-15" + }, + "position": { + "type": "integer", + "example": 1 + } + }, + "type": "object" + } + }, "PublicApiApplicationsRankingViewSchema": { "properties": { "pagination": { @@ -5160,6 +5417,73 @@ }, "type": "object" }, + "CountriesKeywordsCountsViewSchema": { + "description": "Map keyed by 2-letter country code. Each country carries the tracked / recommended / ranking keyword counters and their day-over-day dynamic.", + "type": "object", + "example": { + "US": { + "tracking": { + "count": 120, + "dynamic": 4 + }, + "recommended": { + "count": 35, + "dynamic": -2 + }, + "ranking": { + "count": 5023, + "dynamic": 17 + } + } + }, + "additionalProperties": { + "properties": { + "tracking": { + "properties": { + "count": { + "type": "integer", + "example": 120 + }, + "dynamic": { + "description": "Change versus the previous day.", + "type": "integer", + "example": 4 + } + }, + "type": "object" + }, + "recommended": { + "properties": { + "count": { + "type": "integer", + "example": 35 + }, + "dynamic": { + "description": "Change versus the previous day.", + "type": "integer", + "example": -2 + } + }, + "type": "object" + }, + "ranking": { + "properties": { + "count": { + "type": "integer", + "example": 5023 + }, + "dynamic": { + "description": "Change versus the previous day.", + "type": "integer", + "example": 17 + } + }, + "type": "object" + } + }, + "type": "object" + } + }, "PublicApiFolderNotFoundSchema": { "properties": { "error": { @@ -5843,6 +6167,15 @@ }, "type": "object" }, + "PublicApiRateLimitExceededSchema": { + "properties": { + "error": { + "type": "string", + "example": "Too many requests" + } + }, + "type": "object" + }, "PublicApiRecommendedKeywordViewSchema": { "properties": { "keyword": { @@ -5875,7 +6208,11 @@ }, "PublicApiRecommendedKeywordsListViewSchema": { "properties": { + "pagination": { + "$ref": "#/components/schemas/PublicApiPaginationViewSchema" + }, "keywords": { + "description": "Recommended keywords for the current page.", "type": "array", "items": { "$ref": "#/components/schemas/PublicApiRecommendedKeywordViewSchema" @@ -5941,6 +6278,69 @@ }, "type": "object" }, + "SubscriptionLimitsViewSchema": { + "description": "Plan limits of the authenticated user's current subscription. Each entry carries the plan allowance (`total`) and how much of it is already consumed (`used`).", + "properties": { + "limits": { + "properties": { + "max_keywords": { + "properties": { + "total": { + "type": "integer", + "example": 1000 + }, + "used": { + "type": "integer", + "example": 120 + } + }, + "type": "object" + }, + "max_apps": { + "properties": { + "total": { + "type": "integer", + "example": 10 + }, + "used": { + "type": "integer", + "example": 3 + } + }, + "type": "object" + }, + "max_archived_apps": { + "properties": { + "total": { + "type": "integer", + "example": 5 + }, + "used": { + "type": "integer", + "example": 1 + } + }, + "type": "object" + }, + "max_public_api_tokens": { + "properties": { + "total": { + "type": "integer", + "example": 50000 + }, + "used": { + "type": "integer", + "example": 4200 + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, "PublicApiSuccessSchema": { "properties": { "success": { @@ -6214,6 +6614,10 @@ "name": "Store Charts", "description": "Store Charts" }, + { + "name": "Subscription", + "description": "Subscription" + }, { "name": "Tracking Keywords", "description": "Tracking Keywords"