Applyra API reference: every endpoint
Every endpoint of the Applyra REST API: applications, keywords, positions, competitors, inspections, simulations, niches and top charts, with code examples.
Video transcript
The Applyra API covers everything your account holds. The fastest way in is the question you're answering, not the list of routes. Where do I rank, and did it move? The keyword endpoints, and the daily history behind each one. How well is my listing built? ASO Health audits a tracked app, and the metadata endpoints check and score a draft. Who am I up against? The competitor endpoints. What should I target next? Inspection, autocomplete, niches and top charts, on any term, tracked or not. Each one is in the reference, with examples in cURL, JavaScript and Python. One key works on every route, and every answer has the same shape. A success carries data. A list that pages adds meta: how many pages, and whether more remain. A failure carries a readable error, and a typed code. One market at a time. A tracked app is a store, a country and a language together, so Todoist in three markets is three ids. The API also writes, but only what your account tracks: an app, up to twenty keywords a call, a competitor. Nothing reaches the App Store or Google Play. A script that goes wrong costs you tracking rows, never your listing.
The Applyra REST API lives at https://www.applyra.io/api/v1 and covers everything an account
holds: tracked apps and their listings, keywords with their daily positions and scores,
visibility and ASO Health history, competitors, keyword inspections, metadata simulations,
niche analyses and top charts. It also writes the three things worth automating, which are
tracking an app, adding or removing keywords, and adding or removing competitors.
Every endpoint below is live, authenticated with the personal key found in the API section of your dashboard. The examples show a placeholder key: signed in, the same reference on your API page fills in your own. This page is generated from the same source as the in-app reference, so the two cannot drift apart. If you are starting from nothing, read getting started with the API first, and the recipes for four things worth building on it.
How to pick the endpoint you need
Start from the question you are answering rather than from the route list.
- Where do I rank, and did it move? The keyword endpoints return your tracked terms with their position, traffic score, difficulty and KEI, and the rank history endpoint returns the daily series behind any one of them.
- How well is my listing built? The ASO Health endpoint audits a tracked app. The metadata endpoints check a draft against each store's field rules and score one that does not exist yet.
- Who am I up against? The competitor endpoints list the pairs you follow, and add or remove one.
- What should I target next? Keyword inspection, autocomplete, niche analysis and top charts answer questions about the market rather than about your account, so they work on any term, app or category, tracked or not.
- How much have I used? The account usage endpoint returns your consumption against your effective limits, which is the cheapest call a long running job can make before it starts.
What the reading endpoints have in common
- One key, every route. Authentication is an
X-API-Keyheader or anapi_keyquery parameter, identical everywhere, and it is the same key the MCP server uses. The codes a refused call returns are listed in authentication, rate limits and error codes. - One envelope. A successful response carries the payload under
data. A list that pages adds ametaobject next to it. A failure carries a humanerrorstring and a typedcodeyou can branch on. - Two kinds of identifier. Most routes take Applyra's internal numeric app id, which
GET /api/v1/applicationsreturns and which never changes, so resolve it once and cache it. Tracking a new app is the exception: it takes the store identifier instead, a package name on Google Play, a numeric id or a bundle id on the App Store. - One market at a time. A tracked app is a store, a country and a language together, so an endpoint that reaches into a market asks you which one rather than assuming a default. See markets.
- Pagination on the lists that grow.
pageandper_pageare accepted by the tracked keywords endpoint and by the inspection, autocomplete, simulation and niche history endpoints, and themetaobject tells you how many pages there are and whether more remain. - A null rank means not ranked. It never means unknown. Applyra errors rather than returning partial ranking data, so a null is a fact you can write into a report. See ranking data.
What the API can write
Reading is most of it. The routes that write change only what your account tracks: tracking a new app from its store identifier, adding up to 20 keywords to an app in one call, removing a keyword, marking one as a favorite, and creating or deleting a competitor pair. A keyword added this way has its position read from the store immediately, exactly as if you had typed it into the tracker.
Nothing in the API writes to the App Store or to Google Play. Applyra changes what is measured, never your published listing, so a script that goes wrong costs you tracking rows and nothing on the store.
GETGET /api/v1/account/usage
Returns your current usage statistics and plan limits
curl -H "X-API-Key: YOUR_API_KEY" \
https://www.applyra.io/api/v1/account/usageconst response = await fetch(
'https://www.applyra.io/api/v1/account/usage',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const data = await response.json();import requests
response = requests.get(
'https://www.applyra.io/api/v1/account/usage',
headers={'X-API-Key': 'YOUR_API_KEY'}
)Response
{
"data": {
"plan": "unlimited",
"usage": {
"apps": 5,
"keywords": 150,
"competitors": 20,
"keyword_inspections": 45,
"niche_analyses": 3,
"autocomplete_queries": 88,
"metadata_simulations": 12,
"api_requests": 234
},
"limits": {
"apps": null,
"keywords": null,
"competitors": null,
"keyword_inspections_per_month": null,
"niche_analyses_per_month": null,
"autocomplete_queries_per_month": null,
"metadata_simulations_per_month": null,
"api_requests_per_month": 10000,
"historical_data_days": null,
"export_csv": true,
"export_pdf": true,
"ai_inspections": true
}
}
}GETGET /api/v1/applications
Returns all applications associated with your account
Query Parameters
app_id(optional) - Filter by application ID
curl -H "X-API-Key: YOUR_API_KEY" \
https://www.applyra.io/api/v1/applicationsconst response = await fetch(
'https://www.applyra.io/api/v1/applications',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const data = await response.json();
console.log(data);import requests
response = requests.get(
'https://www.applyra.io/api/v1/applications',
headers={'X-API-Key': 'YOUR_API_KEY'}
)
data = response.json()
print(data)Response
{
"data": [
{
"id": 123,
"app_id": "com.example.app",
"title": "My Application",
"description": "App description...",
"store": "GPLAY",
"country": "US",
"lang": "en-US",
"url": "https://play.google.com/store/apps/...",
"icon": "https://...",
"screenshots": ["https://...", "https://..."],
"developer_name": "My Company",
"genre": "Productivity",
"version": "2.1.0",
"score": 4.5,
"ratings": 12345,
"keyword_count": 25,
"aso_health": {
"global": 68,
"coverage": 63,
"targeting": 100,
"appeal": 51,
"tier": 3,
"tier_label": "Established",
"computed_at": "2026-09-11T17:13:29.662Z"
}
}
],
"meta": { "total": 1 }
}POSTPOST /api/v1/applications
Track a new application by its store bundle ID
Body Parameters (JSON, all required)
app_id- Store bundle ID (e.g.com.spotify.musicfor GPLAY,284882215orcom.facebook.Facebookfor ITUNES)store- GPLAY or ITUNEScountry- Supported ISO country code for the storelang- Supported language code for the store
Application metadata is fetched from the store before the app is linked to your workspace.
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"app_id":"com.spotify.music","store":"GPLAY","country":"US","lang":"en-US"}' \
https://www.applyra.io/api/v1/applicationsconst response = await fetch(
'https://www.applyra.io/api/v1/applications',
{
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
app_id: 'com.spotify.music',
store: 'GPLAY',
country: 'US',
lang: 'en-US'
})
}
);import requests
response = requests.post(
'https://www.applyra.io/api/v1/applications',
headers={'X-API-Key': 'YOUR_API_KEY'},
json={
'app_id': 'com.spotify.music',
'store': 'GPLAY',
'country': 'US',
'lang': 'en-US'
}
)Response
{
"data": {
"id": 123,
"app": {
"id": "com.spotify.music",
"title": "Spotify: Music and Podcasts"
}
}
}GETGET /api/v1/applications/{id}/scores/history
Returns the daily visibility score history of an application
Path Parameters
id- Application ID (from /api/v1/applications)
Query Parameters
from(optional) - Start date (YYYY-MM-DD), defaults to 30 days agoto(optional) - End date (YYYY-MM-DD), defaults to today
Note: Historical data is unlimited on the Unlimited plan.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/applications/123/scores/history?from=2024-01-01&to=2024-01-31"const params = new URLSearchParams({
from: '2024-01-01',
to: '2024-01-31'
});
const response = await fetch(
`https://www.applyra.io/api/v1/applications/123/scores/history?${params}`,
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.get(
'https://www.applyra.io/api/v1/applications/123/scores/history',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'from': '2024-01-01', 'to': '2024-01-31'}
)Response
{
"data": {
"app_id": 123,
"history": [
{
"date": "2024-01-01",
"score": 4.52,
"ratings": 12340
},
{
"date": "2024-01-02",
"score": 4.53,
"ratings": 12358
}
]
},
"meta": {
"from": "2024-01-01",
"to": "2024-01-31",
"total": 31
}
}GETGET /api/v1/applications/{id}/aso-health
The full ASO Health audit of a listing: the three axes, every field, and what to fix
ASO Health grades how the listing is WRITTEN, which is not the same question as how it currently ranks. Three axes, combined as a geometric mean so a zero anywhere caps the whole score: coverage (how much searched vocabulary the indexed fields carry), targeting (whether the terms it aims at are winnable at this app's size) and appeal (rating, screenshots, freshness). The scalars alone are on every row of /api/v1/applications; this is the breakdown behind them.
Path Parameters
id- Application ID (from /api/v1/applications)
Query Parameters
include_words(optional) -trueadds the per-word table of each field: every indexed word with its traffic, difficulty and status
Quotas: consumes 1 API request.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/applications/123/aso-health"const response = await fetch(
'https://www.applyra.io/api/v1/applications/123/aso-health',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data } = await response.json();
console.log(data.report.global, data.report.flags);import requests
response = requests.get(
'https://www.applyra.io/api/v1/applications/123/aso-health',
headers={'X-API-Key': 'YOUR_API_KEY'}
)
report = response.json()['data']['report']
print(report['global'], report['flags'])Response
{
"data": {
"app": {
"id": 123,
"app_id": "com.example.app",
"title": "My Application",
"store": "ITUNES",
"country": "FR",
"lang": "fr-FR"
},
"report": {
"global": 68,
"tier": 3,
"tier_label": "Established",
"computed_at": "2026-09-11T17:13:29.662Z",
"axes": [
{ "key": "coverage", "score": 63 },
{ "key": "targeting", "score": 100 },
{ "key": "appeal", "score": 51 }
],
"fields": [
{
"key": "title",
"label": "Title",
"hint": "Indexed, and by far the heaviest field in the ranking.",
"score": 74,
"state": "scored",
"length": 30,
"limit": 30,
"text": "Marmiton : recettes de cuisine",
"truncated": false
}
],
"targeting": {
"assessable": true,
"keywords": [
{ "name": "recettes", "field": "title", "traffic": 61, "difficulty": 44, "verdict": "reachable" }
],
"ungraded": 0,
"unsearched": 1,
"unsearched_words": ["marmiton"]
},
"appeal": {
"rating": { "value": 4.7 },
"screenshots": { "count": 6, "target": 8 },
"freshness": { "days_since_update": 96 }
},
"flags": [
{
"code": "LISTING_STALE",
"axis": "appeal",
"severity": "warning",
"title": "Your listing has not changed in 96 days",
"impact": "...",
"fix": "...",
"evidence": null
}
]
},
"timeline": [
{ "date": "2026-06-25T07:22:51.059Z", "version": "1.5.0", "global": 69 },
{ "date": "2026-05-22T14:46:50.523Z", "version": "1.4.0", "global": 70 }
]
}
}report is null on an app the audit has never reached, which is not the same as a score of zero.
tier is the app's maturity, 1 to 3 (tier_label: Emerging, Growing, Established). It is what decides which keywords count as winnable: the same term is ambitious for an Emerging app and routine for an Established one.
fields[].state is scored, empty (the field is blank on the store) or missing (the iOS keywords field, which the store never publishes: it is only known if you have given it to us).
targeting.keywords[].verdict is reachable, ambitious or out-of-reach, and the threshold moves with the tier: the same difficulty can be ambitious for a Growing app and out of reach for an Emerging one, and an Established app can reasonably aim higher. traffic and difficulty are both on a 0-100 scale.
targeting.ungraded counts searched terms we hold no competition data for; targeting.unsearched counts words nobody types, listed in unsearched_words, which have no targeting to judge at all. targeting.assessable is false when the listing holds no searched term whatsoever.
fields[].text is a preview, truncated to keep the payload small, and truncated says when it was cut. The full text is on GET /api/v1/applications.fields[].key stays subtitle on Google Play, where the store itself calls that field the short description.
With include_words=true, each words[].status is one of: earning (real search demand, and this field is credited for it), already-used (an earlier field claimed it; the stores index a word once), not-searched (indexed, but nobody types it here), pending (no search data yet on this storefront), and on the Google Play long description covered / missing (does it repeat what the title and short description target) and extra-reach (a searched word it carries that no other field does). occurrences is counted only on that long description, where repetition is visible; it is null elsewhere, because a word is either in the field or not.
POSTPOST /api/v1/metadata/check
Check draft listing text against each store's limits and forbidden copy. Instant, no store lookup
Counts each field the way the STORE counts it: UTF-16 code units after newline normalization, which is neither bytes nor what most languages return for a character count. It also reports the copy each store refuses (price and promotional claims, ranking claims), emoji where they are rejected, and on iOS it breaks the keywords field into its terms. It is pure arithmetic over the text you post, with no store lookup behind it, so it answers in milliseconds: run it on every draft and call /metadata/simulate only once it comes back valid.
Body Parameters (JSON)
store(required) -GPLAYorITUNEStitle,subtitle,description,kw_field- at least one.subtitleis the subtitle on iOS and the short description on Google Play;kw_fieldis iOS only
Quotas: consumes 1 API request.
curl -X POST "https://www.applyra.io/api/v1/metadata/check" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"store":"GPLAY","title":"Betoven - Free Bets #1"}'const response = await fetch(
'https://www.applyra.io/api/v1/metadata/check',
{
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ store: 'GPLAY', title: 'Betoven - Free Bets #1' })
}
);
const { data } = await response.json();
if (!data.valid) console.log(data.fields[0].warnings);import requests
response = requests.post(
'https://www.applyra.io/api/v1/metadata/check',
headers={'X-API-Key': 'YOUR_API_KEY'},
json={'store': 'GPLAY', 'title': 'Betoven - Free Bets #1'}
)
data = response.json()['data']
if not data['valid']:
print(data['fields'][0]['warnings'])Response
{
"data": {
"store": "GPLAY",
"valid": false,
"fields": [
{
"field": "title",
"store_field": "App name",
"indexed": true,
"length": 22,
"limit": 30,
"remaining": 8,
"over": false,
"warnings": [
{
"level": "error",
"message": ""free" is promotional. Google Play's Metadata policy bans price and promotional information in the title...",
"rule": "price"
},
{
"level": "error",
"message": ""#1" claims a ranking. Google Play's Metadata policy bans text that indicates store performance or ranking...",
"rule": "ranking"
}
]
}
],
"notes": []
}
}On the iOS keywords field the response also carries a keywords object: the terms as Apple reads them, the duplicates, the one-character terms, phrases (multi-word entries, which Apple splits apart and recombines itself, so they gain nothing over their words), and the characters wasted on spaces after commas.
warnings[].level is error (the store would refuse this as it stands), warning (it costs you something, or a reviewer may object) or info, which is often the counter-intuitive one: emoji are allowed in a Google Play description and banned in the app name. warnings[].rule names the policy when one fired (price, ranking, play-program, call-to-action, kids, rival-platform) and is null for a limit or formatting warning.
The two stores are not symmetrical, and it is the most actionable thing here: Google names forbidden words and rejects, so the same term is an error there, while Apple publishes no list and a reviewer decides, so it is a warning. A draft can be valid on one store and refused on the other.
valid is false only when a field carries an error: a valid draft can still carry warnings worth acting on. notes carries anything wrong with the request itself, such as sending a keywords field to Google Play, which has none.
POSTPOST /api/v1/metadata/simulate
Score a listing that does not exist yet, against your app's current score
Runs a draft through the same engine that audits real listings. With app_id, every field you leave out is read from that app's live listing and context, so { "app_id": 123, "title": "..." } is a complete request: change one thing and see what it is worth. Without it, pass store, country and lang to score a listing from scratch.
Without an app, the score is not comparable to a real one. The context then defaults to a listing nobody has published: no rating, no screenshot, one language. That deliberately floors the appeal axis, so the overall score is lower than any live app's and must not be read as a verdict on the text. Compare the coverage axis instead, or describe the app you have in mind with context, which works in both modes: any key you send overrides, any key you omit keeps the app's real value, or the blank default when there is no app.
Expect a few seconds per call. The run measures the real search demand behind every word of the draft, on the country and language of that listing, and a word nobody has looked up on that storefront yet has to be read from the store. Change something meaningful between two calls rather than polling it. Runs are saved to the same history the Metadata Simulator shows, so your work is not invisible to whoever owns the account.
Body Parameters (JSON)
app_id(optional) - Application internal ID. Fills every omitted field, and is what the delta is measured againststore,country,lang- required only withoutapp_id; with one, the market comes from the apptitle,subtitle,description,kw_field(optional) - the draftcontext(optional) -rating,rating_count,screenshots,days_since_update,age_months,has_video,language_count. Merged over the app's real values, so one key alone asks a what-if
Quotas: consumes 1 API request.
curl -X POST "https://www.applyra.io/api/v1/metadata/simulate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"app_id":123,"title":"Marmiton : recettes et menus"}'const response = await fetch(
'https://www.applyra.io/api/v1/metadata/simulate',
{
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
app_id: 123,
title: 'Marmiton : recettes et menus'
})
}
);
const { data } = await response.json();
console.log(data.aso_health.global, data.delta.global);import requests
response = requests.post(
'https://www.applyra.io/api/v1/metadata/simulate',
headers={'X-API-Key': 'YOUR_API_KEY'},
json={'app_id': 123, 'title': 'Marmiton : recettes et menus'}
)
data = response.json()['data']
print(data['aso_health']['global'], data['delta']['global'])Response
{
"data": {
"app": {
"id": 123,
"app_id": "com.example.app",
"title": "Marmiton : recettes de cuisine",
"store": "ITUNES",
"country": "FR",
"lang": "fr-FR"
},
"store": "ITUNES",
"country": "FR",
"lang": "fr-FR",
"listing": {
"title": "Marmiton : recettes et menus",
"subtitle": "Cuisine et boissons",
"description": "...",
"kw_field": "recette,cuisine,repas"
},
"aso_health": {
"global": 70,
"coverage": 66,
"targeting": 100,
"appeal": 51,
"tier": 3,
"tier_label": "Established"
},
"delta": { "global": 2, "coverage": 3, "targeting": 0, "appeal": 0 },
"flags": [
{ "code": "SUBTITLE_DUPLICATES_TITLE", "axis": "coverage", "severity": "warning", "title": "...", "impact": "...", "fix": "...", "evidence": null }
],
"words_probed": 12,
"words_fetched": 1,
"saved": { "id": 69, "is_new": true },
"notes": []
}
}app is null for a draft scored from scratch, and delta is null with it. Each of the delta's axes is independently null when it was never computed on the app.
words_probed is how many words the run weighed; words_fetched is how many of those had to be read from the store for the first time. A words_fetched of 0 is why a call came back in a second; a high one is where the seconds went, and it leaves those words measured for every later call.
saved.id is the run's row: pass it to /metadata/simulate/history/{id} to read the whole draft back. notes carries anything wrong with the request that did not stop the run, such as a kw_field sent for a Google Play listing, which has none and is ignored.
GETGET /api/v1/metadata/simulate/history
Listing drafts already scored on the account, newest first
One row per distinct draft, whether it was scored from the API or from the Metadata Simulator in the dashboard. Re-running the same draft updates its row and bumps runs_count rather than adding a line.
Query Parameters
page(optional) - Page number, default 1per_page(optional) - Results per page, default 50, max 200search(optional) - Match on the draft title
Quotas: consumes 1 API request.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/metadata/simulate/history?per_page=20"const response = await fetch(
'https://www.applyra.io/api/v1/metadata/simulate/history?per_page=20',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data, meta } = await response.json();import requests
response = requests.get(
'https://www.applyra.io/api/v1/metadata/simulate/history',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'per_page': 20}
)
data = response.json()['data']Response
{
"data": [
{
"id": 69,
"app_id": 123,
"store": "ITUNES",
"country": "FR",
"lang": "fr-FR",
"title": "Marmiton : recettes et menus",
"aso_health": { "global": 70, "coverage": 66, "targeting": 100, "appeal": 51 },
"baseline_global": 68,
"edited": true,
"runs_count": 2,
"last_run_at": "2026-09-16T14:57:13.405Z"
}
],
"meta": {
"total": 27,
"page": 1,
"per_page": 20,
"total_pages": 2,
"has_more": true
}
}edited is false when the draft was the app's live listing, scored untouched, and baseline_global is the app's score at the time of the run, not today's. Rows carry the title only: to read a draft's other fields, pass its id to the endpoint below.
GETGET /api/v1/metadata/simulate/history/{id}
One saved draft in full: its four fields, the context it assumed, and its findings
What the history list cannot carry. The list is titles and scores; this is the text, so a draft scored last week can be picked up and edited rather than retyped. It also gives the saved.id returned by a simulation somewhere to go.
Path Parameters
id- The run id, from the history list or from a simulation'ssaved.id
Quotas: consumes 1 API request.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/metadata/simulate/history/69"const response = await fetch(
'https://www.applyra.io/api/v1/metadata/simulate/history/69',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data } = await response.json();
console.log(data.listing.subtitle, data.aso_health.global);import requests
response = requests.get(
'https://www.applyra.io/api/v1/metadata/simulate/history/69',
headers={'X-API-Key': 'YOUR_API_KEY'}
)
data = response.json()['data']
print(data['listing']['subtitle'], data['aso_health']['global'])Response
{
"data": {
"id": 69,
"app_id": 123,
"store": "ITUNES",
"country": "FR",
"lang": "fr-FR",
"listing": {
"title": "Marmiton : recettes et menus",
"subtitle": "Cuisine et boissons",
"description": "...",
"kw_field": "recette,cuisine,repas"
},
"kw_field_known": true,
"context": {
"rating": 4.7,
"rating_count": 120000,
"screenshots": 6,
"days_since_update": 96,
"age_months": 108,
"has_video": false,
"language_count": 3
},
"aso_health": {
"global": 70,
"coverage": 66,
"targeting": 100,
"appeal": 51,
"tier": 3,
"tier_label": "Established"
},
"delta": { "global": 2, "coverage": 3, "targeting": 0, "appeal": 0 },
"baseline_global": 68,
"edited": true,
"words_probed": 12,
"runs_count": 2,
"last_run_at": "2026-09-16T14:57:13.405Z",
"report": {
"global": 70,
"tier": 3,
"tier_label": "Established",
"axes": [{ "key": "coverage", "score": 66 }],
"fields": [{ "key": "title", "state": "scored", "length": 28, "limit": 30, "truncated": false }],
"targeting": { "assessable": true, "keywords": [] },
"appeal": { "rating": { "value": 4.7 }, "screenshots": { "count": 6, "target": 10 }, "freshness": { "days_since_update": 96 } },
"flags": [
{ "code": "SUBTITLE_DUPLICATES_TITLE", "axis": "coverage", "severity": "warning", "title": "...", "impact": "...", "fix": "...", "evidence": null }
]
}
}
}kw_field_known is false when the keywords field was empty because Apple never published it, rather than because the draft ships none. The score is not reproducible without it.
report is the same shape /applications/{id}/aso-health returns, findings included, and is null when the stored breakdown predates the current axes. The text and the scores are still there when that happens: only the breakdown goes missing.
POSTPOST /api/v1/competitors
Track a competitor app for one of your applications, identified by its store bundle ID
Body Parameters (JSON, all required)
app_id- Application internal ID (the main app)competitor_app_id- Store bundle ID of the competitor (e.g.com.facebook.katanafor GPLAY,284882215orcom.facebook.Facebookfor ITUNES)store- GPLAY or ITUNEScountry- Supported ISO country code for the storelang- Supported language code for the store
Competitor metadata is fetched from the store, then ranking entries are backfilled for every keyword tracked on the main app.
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"app_id":123,"competitor_app_id":"com.spotify.music","store":"GPLAY","country":"US","lang":"en-US"}' \
https://www.applyra.io/api/v1/competitorsconst response = await fetch(
'https://www.applyra.io/api/v1/competitors',
{
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
app_id: 123,
competitor_app_id: 'com.spotify.music',
store: 'GPLAY',
country: 'US',
lang: 'en-US'
})
}
);import requests
response = requests.post(
'https://www.applyra.io/api/v1/competitors',
headers={'X-API-Key': 'YOUR_API_KEY'},
json={
'app_id': 123,
'competitor_app_id': 'com.spotify.music',
'store': 'GPLAY',
'country': 'US',
'lang': 'en-US'
}
)Response
{
"data": {
"id": 789,
"competitor": {
"id": 4521,
"app_id": "com.spotify.music",
"title": "Spotify: Music and Podcasts"
}
}
}DELETEDELETE /api/v1/competitors/{id}
Remove a competitor relationship by its internal ID
Path Parameters
id- Competitor relation internal ID (theidfield returned by GET /api/v1/competitors or POST /api/v1/competitors)
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" \
https://www.applyra.io/api/v1/competitors/789const response = await fetch(
'https://www.applyra.io/api/v1/competitors/789',
{
method: 'DELETE',
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.delete(
'https://www.applyra.io/api/v1/competitors/789',
headers={'X-API-Key': 'YOUR_API_KEY'}
)Response
{
"data": {
"id": 789,
"removed": true
}
}GETGET /api/v1/competitors
Returns all competitor relationships for your applications
Query Parameters
app_id(optional) - Filter by application ID
curl -H "X-API-Key: YOUR_API_KEY" \
https://www.applyra.io/api/v1/competitorsconst response = await fetch(
'https://www.applyra.io/api/v1/competitors',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const data = await response.json();import requests
response = requests.get(
'https://www.applyra.io/api/v1/competitors',
headers={'X-API-Key': 'YOUR_API_KEY'}
)Response
{
"data": [
{
"id": 789,
"app": {
"id": 123,
"app_id": "com.my.app",
"title": "My App",
"icon": "https://...",
"store": "GPLAY"
},
"competitor": {
"id": 456,
"app_id": "com.competitor.app",
"title": "Competitor App",
"description": "Competitor description...",
"url": "https://play.google.com/store/apps/...",
"icon": "https://...",
"screenshots": ["https://...", "https://..."],
"developer_name": "Competitor Inc",
"genre": "Productivity",
"version": "3.0.1",
"score": 4.3,
"ratings": 5678,
"store": "GPLAY"
},
"app_score": 4.5,
"competitor_score": 4.3
}
],
"meta": { "total": 1 }
}GETGET /api/v1/keywords
Returns tracked keywords for your applications. Supports optional pagination.
Query Parameters
app_id(optional) - Filter by application IDfavorites(optional) - Set totrueto return only keywords marked as favoritepage(optional) - Page number, starting from 1. Enables pagination mode.per_page(optional) - Results per page (default: 200, max: 1000). Enables pagination mode.
Note: Without page or per_page, returns up to 1000 results. Use pagination to access all keywords.
# Without pagination (up to 1000 results)
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/keywords?app_id=123"
# With pagination
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/keywords?app_id=123&page=1&per_page=200"// With pagination
const response = await fetch(
'https://www.applyra.io/api/v1/keywords?page=1&per_page=200',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data, meta } = await response.json();
console.log(`Page ${meta.page} of ${meta.total_pages}`);import requests
# With pagination
response = requests.get(
'https://www.applyra.io/api/v1/keywords',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'app_id': 123, 'page': 1, 'per_page': 200}
)Response (without pagination)
{
"data": [
{
"keyword_id": 456,
"keyword": "fitness tracker",
"store": "GPLAY",
"country": "US",
"lang": "en-US",
"difficulty_score": 45,
"traffic_score": 72,
"app_id": 123,
"app_title": "My App",
"current_rank": 15,
"ahead": {
"app_id": "com.competitor.app",
"title": "Competitor App",
"icon": "https://..."
},
"behind": {
"app_id": "com.other.app",
"title": "Other App",
"icon": "https://..."
},
"ranking": [
{ "app_id": "com.top1.app", "title": "Top App", "icon": "https://...", "rank": 1 },
{ "app_id": "com.top2.app", "title": "Second App", "icon": "https://...", "rank": 2 }
],
"is_favorite": false,
"tracked_since": "2024-01-15T12:00:00Z"
}
],
"meta": { "total": 1, "has_more": false }
}Response Fields
current_rank- Your app position for this keyword in the latest daily ranking snapshot.nullmeans your app was not in the top 100 for that keyword.ahead/behind- The apps ranked immediately above and below yours in that same snapshot. Both arenullwhencurrent_rankisnull;aheadisnullat rank 1, andbehindisnullat the last ranked position.ranking- The top 5 apps for this keyword, in ranking order, withrankbeing the position (1 to 5).difficulty_score/traffic_score- Keyword-level ASO scores from 0 to 100, independent of your app.
A rank of null always means "not ranked", never "unknown": if rank data cannot be read, the endpoint returns an error instead of a partial response.
Response (with pagination)
{
"data": [ ... ],
"meta": {
"total": 2350,
"page": 1,
"per_page": 200,
"total_pages": 12,
"has_more": true
}
}POSTPOST /api/v1/keywords
Track up to 20 new keywords for an application in a single call
Body Parameters (JSON, all required)
keywords- Array of 1-20 keyword strings (each 2-100 chars)app_id- Application internal IDstore- GPLAY or ITUNEScountry- Supported ISO country code for the storelang- Supported language code for the store
Each keyword triggers an immediate ranking fetch. Already-tracked keywords return a per-keyword error in results; soft-deleted entries are reactivated automatically.
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"keywords":["fitness tracker","workout planner"],"app_id":123,"store":"GPLAY","country":"US","lang":"en-US"}' \
https://www.applyra.io/api/v1/keywordsconst response = await fetch(
'https://www.applyra.io/api/v1/keywords',
{
method: 'POST',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
keywords: ['fitness tracker', 'workout planner'],
app_id: 123,
store: 'GPLAY',
country: 'US',
lang: 'en-US'
})
}
);
const { data } = await response.json();import requests
response = requests.post(
'https://www.applyra.io/api/v1/keywords',
headers={'X-API-Key': 'YOUR_API_KEY'},
json={
'keywords': ['fitness tracker', 'workout planner'],
'app_id': 123,
'store': 'GPLAY',
'country': 'US',
'lang': 'en-US'
}
)Response
{
"data": {
"results": [
{ "keyword": "fitness tracker", "success": "Keyword \"fitness tracker\" added. Your app ranks #15." },
{ "keyword": "workout planner", "error": "Already tracking this keyword for this application" }
],
"success_count": 1,
"error_count": 1
}
}DELETEDELETE /api/v1/keywords/{id}/apps/{app_id}
Stop tracking a keyword for the specified app (soft delete; idempotent)
Path Parameters
id- Keyword ID (from /api/v1/keywords)app_id- Application internal ID this keyword is tracked for
The historical ranking data is preserved. Re-adding the same keyword reactivates the soft-deleted row.
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" \
https://www.applyra.io/api/v1/keywords/456/apps/123const response = await fetch(
'https://www.applyra.io/api/v1/keywords/456/apps/123',
{
method: 'DELETE',
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.delete(
'https://www.applyra.io/api/v1/keywords/456/apps/123',
headers={'X-API-Key': 'YOUR_API_KEY'}
)Response
{
"data": {
"keyword_id": 456,
"app_id": 123,
"already_removed": false
}
}PATCHPATCH /api/v1/keywords/{id}/apps/{app_id}/favorite
Toggle the favorite flag on a tracked keyword for a specific app
Path Parameters
id- Keyword ID (from /api/v1/keywords)app_id- Application internal ID this keyword is tracked for
Favorites are stored per tracking row (profile + app + keyword). A keyword tracked for several apps has independent favorite states.
Body Parameters (JSON, all required)
is_favorite- Boolean to set the favorite state
curl -X PATCH -H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"is_favorite": true}' \
https://www.applyra.io/api/v1/keywords/456/apps/123/favoriteconst response = await fetch(
'https://www.applyra.io/api/v1/keywords/456/apps/123/favorite',
{
method: 'PATCH',
headers: {
'X-API-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ is_favorite: true })
}
);import requests
response = requests.patch(
'https://www.applyra.io/api/v1/keywords/456/apps/123/favorite',
headers={'X-API-Key': 'YOUR_API_KEY'},
json={'is_favorite': True}
)Response
{
"data": {
"keyword_id": 456,
"app_id": 123,
"is_favorite": true
}
}GETGET /api/v1/keywords/{id}/ranks/history
Returns historical ranking data for a tracked keyword across all your apps
Path Parameters
id- Keyword ID (from /api/v1/keywords)
Query Parameters
from(optional) - Start date (YYYY-MM-DD), defaults to 30 days agoto(optional) - End date (YYYY-MM-DD), defaults to today
Note: Historical data is unlimited on the Unlimited plan.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/keywords/456/ranks/history?from=2024-01-01&to=2024-01-31"const params = new URLSearchParams({
from: '2024-01-01',
to: '2024-01-31'
});
const response = await fetch(
`https://www.applyra.io/api/v1/keywords/456/ranks/history?${params}`,
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.get(
'https://www.applyra.io/api/v1/keywords/456/ranks/history',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'from': '2024-01-01', 'to': '2024-01-31'}
)Response
{
"data": {
"keyword_id": 456,
"keyword": "fitness tracker",
"store": "GPLAY",
"country": "US",
"apps": [
{
"app_id": 123,
"app_title": "My Fitness App",
"history": [
{ "date": "2024-01-01", "rank": 15 },
{ "date": "2024-01-02", "rank": 14 },
{ "date": "2024-01-03", "rank": 12 }
]
}
]
},
"meta": {
"from": "2024-01-01",
"to": "2024-01-31",
"total_apps": 1
}
}GETGET /api/v1/keywords/inspect
Analyze a keyword to get difficulty, traffic, and ranking data
Query Parameters (all required)
keyword- The keyword to analyzestore- GPLAY or ITUNEScountry- ISO country code (e.g., US, FR)lang- Language code (e.g., en-US, fr-FR)
Quotas: consumes 1 API request.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/keywords/inspect?keyword=fitness&store=GPLAY&country=US&lang=en-US"const params = new URLSearchParams({
keyword: 'fitness',
store: 'GPLAY',
country: 'US',
lang: 'en-US'
});
const response = await fetch(
`https://www.applyra.io/api/v1/keywords/inspect?${params}`,
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.get(
'https://www.applyra.io/api/v1/keywords/inspect',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={
'keyword': 'fitness',
'store': 'GPLAY',
'country': 'US',
'lang': 'en-US'
}
)Response
{
"data": {
"keyword": "fitness",
"store": "GPLAY",
"country": "US",
"lang": "en-US",
"difficulty_score": 45,
"traffic_score": 72,
"kei": { "score": 1.6, "level": "excellent" },
"ranking": [
{
"rank": 1,
"app_id": "com.example.fitness",
"title": "Fitness Pro",
"icon": "https://...",
"genre": "Health & Fitness",
"rating": 4.7
}
],
"related_keywords": ["fitness app", "workout"],
"is_tracked": false,
"user_tracking": []
}
}GETGET /api/v1/keywords/inspect/history
Returns the history of keywords you have inspected
Query Parameters
page(optional) - Page number (default 1)per_page(optional) - Results per page (default 50, max 200)
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/keywords/inspect/history?page=1&per_page=50"const response = await fetch(
'https://www.applyra.io/api/v1/keywords/inspect/history?page=1&per_page=50',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data, meta } = await response.json();import requests
response = requests.get(
'https://www.applyra.io/api/v1/keywords/inspect/history',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'page': 1, 'per_page': 50}
)Response
{
"data": [
{
"id": 12,
"keyword_name": "fitness tracker",
"store": "GPLAY",
"country": "US",
"lang": "en-US",
"difficulty_score": 45,
"traffic_score": 72,
"last_inspected_at": "2024-03-12T08:00:00Z"
}
],
"meta": {
"total": 23,
"page": 1,
"per_page": 50,
"total_pages": 1,
"has_more": false
}
}GETGET /api/v1/autocomplete
Run an autocomplete suggestion query against the App Store or Google Play
Query Parameters (all required)
store- GPLAY or ITUNEScountry- ISO country code (e.g., US, FR)lang- Language code (e.g., en-US, fr-FR)prefix- Search prefix (1-60 characters)
Quotas: consumes 1 API request.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/autocomplete?store=GPLAY&country=US&lang=en-US&prefix=fitness"const params = new URLSearchParams({
store: 'GPLAY',
country: 'US',
lang: 'en-US',
prefix: 'fitness'
});
const response = await fetch(
`https://www.applyra.io/api/v1/autocomplete?${params}`,
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.get(
'https://www.applyra.io/api/v1/autocomplete',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={
'store': 'GPLAY',
'country': 'US',
'lang': 'en-US',
'prefix': 'fitness'
}
)Response
{
"data": {
"suggestions": [
"fitness tracker",
"fitness app",
"fitness coach"
],
"normalized_prefix": "fitness",
"store": "GPLAY",
"country": "US",
"lang": "en-US"
}
}GETGET /api/v1/autocomplete/history
Returns the history of your autocomplete queries
Query Parameters
page(optional) - Page number (default 1)per_page(optional) - Results per page (default 50, max 200)
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/autocomplete/history?page=1&per_page=50"const response = await fetch(
'https://www.applyra.io/api/v1/autocomplete/history?page=1&per_page=50',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data, meta } = await response.json();import requests
response = requests.get(
'https://www.applyra.io/api/v1/autocomplete/history',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'page': 1, 'per_page': 50}
)Response
{
"data": [
{
"id": 7,
"prefix": "fitness",
"store": "GPLAY",
"country": "US",
"lang": "en-US",
"suggestion_count": 8,
"last_queried_at": "2024-03-12T08:00:00Z"
}
],
"meta": {
"total": 12,
"page": 1,
"per_page": 50,
"total_pages": 1,
"has_more": false
}
}GETGET /api/v1/niches
Run a synchronous niche analysis. Returns the full clustered result.
Query Parameters (all required)
topic- Niche topic (2-100 characters)store- GPLAY or ITUNEScountry- Supported ISO country code for the storelang- Supported language code for the store
Quotas: consumes 1 API request. Fresh analyses can take up to a few minutes.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/niches?topic=meditation&store=GPLAY&country=US&lang=en-US"const params = new URLSearchParams({
topic: 'meditation',
store: 'GPLAY',
country: 'US',
lang: 'en-US'
});
const response = await fetch(
`https://www.applyra.io/api/v1/niches?${params}`,
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data, meta } = await response.json();import requests
response = requests.get(
'https://www.applyra.io/api/v1/niches',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={
'topic': 'meditation',
'store': 'GPLAY',
'country': 'US',
'lang': 'en-US'
},
timeout=360
)Response
{
"data": {
"topic": "meditation",
"store": "GPLAY",
"country": "US",
"lang": "en-US",
"totalKeywords": 120,
"analyzedAt": "2024-03-12T08:00:00Z",
"clusters": [
{
"name": "Sleep meditation",
"appConcept": "An app focused on guided meditations for sleep",
"intentType": "utility",
"opportunityScore": 0.78,
"keywords": [
{ "name": "sleep meditation", "trafficScore": 65, "difficultyScore": 32 }
]
}
]
},
"meta": { "cached": false }
}GETGET /api/v1/niches/history
Returns the history of niche analyses you have run
Query Parameters
page(optional) - Page number (default 1)per_page(optional) - Results per page (default 50, max 200)
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/niches/history?page=1&per_page=50"const response = await fetch(
'https://www.applyra.io/api/v1/niches/history?page=1&per_page=50',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);
const { data, meta } = await response.json();import requests
response = requests.get(
'https://www.applyra.io/api/v1/niches/history',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'page': 1, 'per_page': 50}
)Response
{
"data": [
{
"id": 4,
"topic": "meditation",
"store": "GPLAY",
"country": "US",
"lang": "en-US",
"clusters_count": 5,
"keywords_count": 120,
"top_opportunity_score": 0.78,
"created_at": "2024-03-12T08:00:00Z"
}
],
"meta": {
"total": 4,
"page": 1,
"per_page": 50,
"total_pages": 1,
"has_more": false
}
}GETGET /api/v1/top-charts
Get a store top-chart ranking by country, category and collection, with daily rank movement
Query Parameters
store- GPLAY or ITUNES (required)country- ISO country code, e.g. US, FR (required)category- OVERALL (default) or a store category value (iTunes genre id e.g. 6014, Google Play category e.g. GAME)collection- free (default), paid or grossinglimit- max rows, default 100, max 200
Quotas: consumes 1 API request.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/top-charts?store=ITUNES&country=US&category=OVERALL&collection=free"const params = new URLSearchParams({
store: 'ITUNES',
country: 'US',
category: 'OVERALL',
collection: 'free'
});
const response = await fetch(
`https://www.applyra.io/api/v1/top-charts?${params}`,
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.get(
'https://www.applyra.io/api/v1/top-charts',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={
'store': 'ITUNES',
'country': 'US',
'category': 'OVERALL',
'collection': 'free'
}
)Response
{
"data": {
"store": "ITUNES",
"country": "US",
"category": "OVERALL",
"collection": "free",
"snapshot_date": "2026-06-19",
"total_count": 200,
"rows": [
{
"rank": 1,
"app_id": "com.openai.chat",
"apple_id": 6448311069,
"title": "ChatGPT",
"developer": "OpenAI",
"icon": "https://...",
"rating": 4.8,
"price": null,
"currency": null,
"delta": 0,
"is_new": false
}
]
},
"meta": { "total": 200 }
}GETGET /api/v1/top-charts/categories
List the categories and collections supported by the top-charts endpoint, per store
Query Parameters
store- ITUNES or GPLAY to filter (optional; omit for both)
Use the returned category key values (e.g. OVERALL, 6014, GAME) and collections as inputs to the top-charts endpoint.
Quotas: consumes 1 API request.
curl -H "X-API-Key: YOUR_API_KEY" \
"https://www.applyra.io/api/v1/top-charts/categories?store=ITUNES"const response = await fetch(
'https://www.applyra.io/api/v1/top-charts/categories?store=ITUNES',
{
headers: { 'X-API-Key': 'YOUR_API_KEY' }
}
);import requests
response = requests.get(
'https://www.applyra.io/api/v1/top-charts/categories',
headers={'X-API-Key': 'YOUR_API_KEY'},
params={'store': 'ITUNES'}
)Response
{
"data": {
"collections": ["free", "paid", "grossing"],
"categories": {
"ITUNES": [
{ "key": "OVERALL", "label": "Top Overall" },
{ "key": "6014", "label": "Games" }
]
}
}
}Frequently asked questions
Does Applyra have a public API?
Yes. Every app, keyword, position, competitor and score in your account is readable over a versioned REST API at /api/v1, authenticated with a personal key. It also writes: an app can be tracked, and keywords and competitors added or removed, over HTTP.
Which plan includes API access?
The API is part of the paid plan, with 10,000 requests a month. Free accounts have no API access, and every call returns a 403 with PLAN_REQUIRED.
Can I add keywords to my Applyra account through the API?
Yes. A single POST to /api/v1/keywords tracks up to 20 keywords for one app, and each one has its position read from the store straight away. A DELETE on the same family stops tracking a keyword while keeping the history already measured.
How do I page through a long list in the Applyra API?
Send page and per_page on the list endpoints. The response carries a meta object with the total number of rows, the page you are on, the number of pages and whether more remain, so a client can loop until there are none left.
Last updated on