Skip to main content
Glama

AppTail – ASO & Keyword Research

Server Details

App Store keyword rankings, competitors, markets, reviews and analytics for iOS apps, from your AI.

If you are the author of this connector, you can claim ownership by verifying the domain or GitHub account it belongs to. Claimed connector authors can inspect health checks, view analytics, and manage their listing.
Status
Healthy
Uptime
95.4% over 22 days
Last Tested
Transport
Streamable HTTP · MCP 2025-11-25
URL

TDQS

A3.9/5.0

Scored across 25 tools

Disambiguation4/5

Descriptions work hard to draw boundaries, e.g. get_keywords explicitly replaces deprecated tools and get_competitors is contrasted with get_landscape. Still, several read/report tools overlap conceptually (get_performance vs get_signals vs explain_period; get_market vs get_landscape vs get_competitors), so an agent could occasionally hesitate between them.

Naming Consistency5/5

Every tool follows a clean verb_noun snake_case pattern (add_app, get_keywords, remove_competitors, save_market, tag_keywords). No mixing of conventions or vague verbs across the set.

Tool Count3/5

25 tools sits at the heavy end for a single server, and the surface spans many subdomains (apps, keywords, markets, competitors, reviews, performance, charts, signals). Each tool is justified by domain breadth, but the set is large enough to be borderline.

Completeness4/5

Strong lifecycle coverage: apps, competitors, keywords and markets each have add/remove/read, reviews have read/reply, plus discovery, signals, charts and performance. Minor gaps like no remove_app/update_app keep it just short of full coverage.

Available Tools

25 tools
add_appAdd AppAInspect

Add an app to the account as one of the user's own. Takes an Apptail app_id, an App Store URL, or a name — resolved and imported the way search_apps does. Tracking starts immediately (rankings, reviews, competitors); measured downloads and proceeds still require connecting App Store Connect in the console, which cannot be done from here.

ParametersJSON Schema
NameRequiredDescriptionDefault
appYesThe app to claim: an Apptail app_id, a full App Store URL, or the app's name. A URL is the reliable form — a name resolves against the whole store, and two apps can share one.
countryNoPrimary storefront for this app, ISO 3166-1 alpha-2 (e.g. US, DE). Defaults to US. This is the storefront its keywords and rankings are tracked in first; more can be added in the console.
dry_runNoResolve the app and report what would happen without claiming it. Worth doing when the user gave a name rather than a URL, so they can confirm it is the right app before it counts against their plan.

Output Schema

ParametersJSON Schema
NameRequiredDescription
appNoThe app as it now appears in the account. Null under dry_run only if nothing resolved.
noteNoWhat happens next, when there is something the user should know.
statusYes`added` when it is now in the account, `already` when it was there before this call, `would_add` under dry_run.
countryYesThe storefront it was added for.
apps_usedYesOwn apps in the account after this call.
connectedYesWhether App Store Connect reports for this app. False for almost every freshly added app — say so, because get_performance will return nothing for it until it is connected in the console.
apps_limitYesHow many the plan allows. -1 means unlimited.
console_urlNoWhere to open this app in the AppTail console.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false, openWorldHint=true, destructiveHint=false. The description goes further, disclosing the immediate side effect (rankings, reviews, competitors begin tracking at once), the billing/plan implication, and an out-of-band dependency (App Store Connect must be connected in the console, not here). That is exactly the behavioral context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the core action, then input forms, then side effects and the ASC limitation. No sentence is filler; each adds a distinct fact an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Safety profile comes from annotations and return shape from the output schema, so the description's job is side effects and prerequisites — both covered. Nothing an agent needs to call this mutating, importing tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the schema sets the floor at 3. The description adds cross-tool semantics by stating all three accepted forms are resolved and imported the same way search_apps resolves them, which tells the agent the input is a resolution target rather than a raw key.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Add an app to the account as one of the user's own') and pinpoints the accepted input forms (app_id, App Store URL, or name). It also distinguishes itself from the sibling search_apps by noting resolution happens 'the way search_apps does', so an agent can tell importing apart from merely searching.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear context: tracking starts immediately, and measured downloads/proceeds require connecting App Store Connect in the console, which 'cannot be done from here' — a real prerequisite. It stops short of an explicit when-not-to-use rule or naming an alternative tool for the pre-check (dry_run covers that only inside the schema).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_competitorsAdd CompetitorsAInspect

Track one or more rival apps against one of YOUR apps. Each competitor can be given as an Apptail app_id, an App Store URL, or just a name — names and URLs are resolved and imported the way search_apps does, so you do NOT need to look up an app_id first. Returns one outcome per item: what was added, what was already tracked, and what a plan limit refused.

ParametersJSON Schema
NameRequiredDescriptionDefault
app_idYesApptail app id for one of YOUR apps — from get_account. The rivals are tracked FOR this app: competitor sets belong to an app, not to the account, so removing one later affects only this app.
countryNoStorefront to resolve names in, ISO 3166-1 alpha-2 (e.g. US, DE). Omit and it is guessed from the script the name is written in. Ignored for items given as an app_id.
dry_runNoResolve every item and report what would happen, without tracking anything. Use it when the user named apps ambiguously and you want to confirm you found the right ones before changing their account.
competitorsYesThe rivals, up to 20. Each item is an Apptail app_id, a full App Store URL, or an app name in any language. Send all of them in one call — the plan limit is checked per item, so a batch that runs out of room still adds what fits and says which items did not.

Output Schema

ParametersJSON Schema
NameRequiredDescription
app_idYesThe app these rivals are tracked against.
dry_runYesTrue when nothing was written. Every outcome then reads `would_add`.
outcomesYesOne row per item, in the order they were sent. Read `status` per row: a batch is rarely all one thing, and reporting it as "done" when two of five were refused is the failure this shape exists to prevent.
remainingNoCompetitor slots left for this app on this plan after the call. Null when the plan is unlimited. Zero means the next add will be refused — say so rather than letting the user find out.
added_countYesHow many are now tracked that were not before.
requested_countYesHow many items were sent.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover safety (readOnly=false, destructive=false, openWorld=true), and the description adds meaningful behavior beyond them: per-item outcome reporting, plan-limit refusal semantics, and that a batch still adds what fits. Good added context on partial success and limit handling.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the core action, then resolution behavior, then return semantics. Every sentence earns its place with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values needn't be spelled out, yet the description still summarizes the per-item outcome structure. Combined with the workflow guidance on avoiding a prior app_id lookup, an agent has everything needed to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents app_id, country, dry_run, and competitors. The description mostly reinforces that resolution accepts app_id/URL/name, which the schema already states; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('track rival apps against one of YOUR apps') and distinguishes itself from get_competitors/remove_competitors by being the add side of competitor tracking. It also clarifies the relationship to search_apps for name/URL resolution.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells the agent it does NOT need to look up an app_id first because names/URLs resolve like search_apps, which is the key workflow decision. It does not name explicit when-not conditions versus siblings, but the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

add_keywordsAdd KeywordsAInspect

Start tracking search terms for one of your apps, in one storefront. Send terms, or keyword_ids for suggestions returned by discover_keywords — a suggestion MUST be added by id, because re-resolving its term can land on a different keyword row. Counts against the plan's per-app keyword limit; dry_run reports what would happen without writing anything. Terms already in the corpus come back under already_tracked rather than as an error — that is the state the caller asked for.

ParametersJSON Schema
NameRequiredDescriptionDefault
app_idYesApptail app id for one of YOUR apps — get it from get_account. Not the numeric id in an App Store URL (that is apple_app_id). Keywords are tracked per app.
countryYesStorefront to track these terms in, ISO 3166-1 alpha-2 (e.g. US, GB, DE). Required and not defaulted: a term tracked in the wrong storefront is a wasted keyword slot. Ranks are per storefront — the same term ranks differently in each.
dry_runNoReport what would be added, and how many slots are left, without tracking anything. Nothing is written and nothing counts against the plan.
keywordsNoSearch terms to start tracking, as a user would type them into the App Store (e.g. ["fitness tracker", "workout app"]). Send the whole batch in one call rather than one term per call. Terms we have never seen are created. Every added term whose results are more than a day old is crawled straight away, and positions usually land within a few minutes: a get_keywords read made immediately after this call shows those terms as unmeasured, which means not crawled yet and not that the app is missing from the results. Either this or `keyword_ids` is required.
keyword_idsNoApptail keyword ids to start tracking — the `keyword_id` on a discover_keywords row. Use this rather than the term whenever you have an id: a term is re-resolved and can land on a different keyword row than the one you were shown.

Output Schema

ParametersJSON Schema
NameRequiredDescription
addedYesTerms now being tracked. Under `dry_run` this is what would be added and nothing was written.
limitYesKeywords this plan allows per app. -1 means unlimited.
app_idYesThe app these terms are tracked for.
countryYesThe storefront they were tracked in.
dry_runYesTrue when nothing was written.
skippedYesEverything not added: the union of `already_tracked` and the terms that could not be resolved in that storefront or that ran out of plan room. Do not report this count as a failure without reading `already_tracked` first.
remainingNoKeyword slots left for this app on this plan after the call. Null when the plan is unlimited. Zero means the next add is refused.
added_countYesSize of `added`.
limit_reachedNoSet when the account's plan limit stopped the run before the whole list was added. A ceiling, not a fault — say what was capped rather than retrying.
skipped_countYesSize of `skipped`, both reasons together. Subtract `already_tracked` before calling anything a problem.
already_trackedYesThe subset of `skipped` that the app was already tracking. **This is the end state that was asked for, not a fault** — a term already in the corpus needs no retry and no apology. What is in `skipped` but not here is the half worth mentioning: a term that does not exist in that storefront, or one the plan had no room for (see `limit_reached`).

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say it is a non-destructive write in an open world; the description goes much further by disclosing plan-limit consumption, the no-write guarantee of dry_run, the idempotent 'already_tracked' outcome, and the immediate-crawl/unmeasured read-after-write behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first clause, and the remaining sentences each carry a distinct operational fact (id requirement, quota, dry_run, already_tracked). No filler despite the density.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter mutation with an output schema already handling return shape, the description covers everything an agent needs: quota implications, batching guidance, id ambiguity, dry-run semantics, and the non-error duplicate case.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and its field descriptions are already detailed, so baseline is 3. The description still adds cross-cutting semantics the schema cannot: that a re-resolved term can land on a different keyword row, justifying the id-vs-term choice, though it adds little on app_id/country/dry_run.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource+scope: 'Start tracking search terms for one of your apps, in one storefront.' It is immediately distinguishable from siblings like discover_keywords (suggestion source) and remove_keywords (inverse operation).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit routing rules: send terms OR keyword_ids from discover_keywords, with the hard condition that a suggestion MUST be added by id. Also flags the plan limit, the dry_run escape hatch, and that already-tracked terms are not an error.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

discover_keywordsDiscover KeywordsAInspect

Terms an app could be tracking but is not, merged from every source and each labelled with where it came from: what tracked competitors rank for, what the store's similar apps rank for, what those competitors put in their own titles and subtitles, what the store's own mining surfaced, and — reading the listing cold — what the app's own title, subtitle and description suggest. The last is the only source that works on an app added minutes ago; listing is the only one that can surface a phrase nobody has ranked for yet, and it offers a phrase only when two or more rivals publish it and Apple reports somebody searching it. Pass contains to also match terms already in AppTail's database. Already-tracked and already-dismissed terms are never returned. This is a list of candidates, not a measure of coverage: for how much of a niche's vocabulary the account is missing, and which holes matter most, call get_landscape and read term_gap. Discovery can persist shared suggested keyword records, cache generated suggestions, and queue background popularity collection. It does not add those terms to account tracking.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax suggestions, most-searched first. Default 60, capped at 200. The answer always says how many there were.
app_idYesApptail app id to suggest for. get_account for the user's own apps. Suggestions are derived from this app's listing, its rivals and its neighbours, so they are only meaningful for the app they were asked about.
sourceNoWhich sources to read. Omit for all five that need no argument — `competitors`, `similar`, `listing`, `store`, `ai`. `corpus` is a text match against terms AppTail already holds and does nothing without `contains`, which is why "all" does not silently include it. An unknown name is refused rather than ignored.
countryNoStorefront to suggest for (e.g. US, DE). Defaults to the app's primary storefront — which is only one of them: an app localized into several languages is searched for in several storefronts, so call once per entry in get_app's `listing_storefronts`. Discovery is per storefront and never a blend: a German term is not a translation of an English one — `spritverbrauch` beats `kraftstoffverbrauch` by a margin no dictionary would tell you.
containsNoOnly terms containing this text. With `source: ["corpus"]` this is a search of AppTail's keyword database. Note it searches terms AppTail holds, not the App Store: a phrase nobody has ever tracked returns nothing, and to start tracking a brand-new term you pass it straight to add_keywords.

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYesSuggestions returned.
totalYesSuggestions found before the cap.
app_idYesThe app these suggestions are for.
countryYesThe storefront they apply to.
sourcesYesSources actually read. A source not listed here was not consulted.
app_nameNoIts name.
by_sourceYesHow many suggestions each source produced, keyed by source name; a source with none is absent. **Read this before reporting an empty answer.** Five of the six need something on file — rivals, the store's similar-apps list, a mining job that runs fortnightly — so zero beside `competitors` is a fact about the account, not about the niche, and the fix is add_competitors rather than a different question.
truncatedYesTrue when `count` is below `total`. Say the list is partial rather than presenting it as everything worth tracking.
console_urlNoThe app's keyword screen in the console, where the same list can be reviewed and added by hand.
suggestionsYesThe terms, most-searched first. Add them with add_keywords using their `keyword_id`, never their text.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false, openWorldHint=true, destructiveHint=false. The description goes further, disclosing the actual side effects — persisting shared suggested keyword records, caching suggestions, queueing background popularity collection — and clarifies the boundary that it does not modify account tracking.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense and front-loaded with the core definition and the get_landscape alternative before the caveats. It runs long for a single description and repeats a few points already in the schema, but nearly every sentence carries decision-relevant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation. The description still covers all the complexity an agent needs: the five default sources, why corpus is excluded from 'all', the never-returned already-tracked/dismissed terms, and the per-storefront constraint.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning beyond the schema: `listing` is the only source that can surface an unranked phrase and only when two-plus rivals publish it, `corpus` requires `contains`, and storefront selection is never a blend. Some of this duplicates the schema text, hence not a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — unearthing candidate terms an app could track but is not — and enumerates the provenance of each candidate. It is clearly distinguishable from get_keywords (tracked terms) and get_landscape (coverage gaps), which it names explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Routes the agent to siblings by condition: get_landscape/`term_gap` for coverage measurement, add_keywords to actually track a new term, and it explicitly states discovery does NOT add terms to tracking. It also tells the agent to call once per storefront using get_app's `listing_storefronts`.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_market_corpusEdit Market CorpusA
Destructive
Inspect

Add terms to one storefront's corpus, or take terms out of it. Add for a term the corpus SHOULD hold and does not — a rival's brand, a phrase Apple has never measured the popularity of, the term a thin storefront is really about; a pinned term is kept through every rebuild and refreshed on the same 72-hour cadence as the rest. Remove for a term that does not belong: ANY term can go, and one the heuristic found is also kept out of future rebuilds rather than returning within 72 hours. This is NOT save_market: seeds are what the corpus is expanded FROM and changing one re-runs the whole heuristic over that storefront, where these two writes act on single terms in the result.

ParametersJSON Schema
NameRequiredDescriptionDefault
addNoTerms to pin, as somebody would type them into the App Store. Send the batch in one call. Terms already in the corpus come back under `already_present` rather than as an error.
removeNoKeyword ids to take out of the corpus — the `keyword_id` on ANY get_market corpus row. A term the heuristic found is also recorded as excluded so rebuilds do not re-derive it; `add` on the same term later clears that again.
countryYesWhich storefront's corpus, ISO 3166-1 alpha-2 (e.g. US, DE). Required and not defaulted: a corpus belongs to one storefront, and there is no market-wide term list to add to.
market_idYesApptail market id — from list_markets or save_market.

Output Schema

ParametersJSON Schema
NameRequiredDescription
capYesThe most terms this storefront may hold, from the account's plan. A market's storefronts each get their own — the cap is per storefront, not shared across the market.
termsYesHow many terms this storefront's corpus holds now.
pinnedYesTerms added to the corpus by this call, with `origin: "manual"`. A term Apptail has never seen is created and queued for crawling, so its ranks appear within minutes rather than immediately.
caveatsYesWhat the caller needs to hear before reporting success — that ranks are not in yet, or that the cap cut the list.
countryYesThe storefront that was edited. A corpus belongs to one storefront and is never shared between them.
refusedYesTerms not added because the storefront is at `cap`. Unpin something or drop a storefront; the account's plan sets the number.
removedYesKeyword ids taken out of the corpus by this call.
excludedYesThe subset of `removed` that will ALSO be kept out of every future rebuild. A term the heuristic found is re-derived from the seeds every 72 hours, so removing it has to be recorded outside the corpus to last; a hand-pinned term needs no such record because nothing re-adds it. Pinning a term with `add` clears it from this list again.
market_idYesThe market this corpus belongs to.
console_urlNoThis market's corpus in the console.
not_removedYesIds that were sent to `remove` and are still in the corpus, each with the reason.
already_presentYesTerms that were already in this corpus, and where they came from. Not an error — it is the state the caller asked for, and a `competitor` term is NOT re-recorded as `manual`, because the origin says who put the term here.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructive=true, but the description adds substantial context beyond them: pinned terms survive every rebuild and refresh on a 72-hour cadence, removed heuristic terms are recorded as excluded so they don't re-derive, and a later `add` clears that exclusion. These are non-obvious side effects an agent cannot infer from the structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core add/remove action, then the behavioral details, then the sibling contrast. Dense and mostly waste-free, though the long single paragraph carries a few stacked clauses that could be broken out.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need not be described. The description covers the add/remove semantics, cadence, rebuild persistence, exclusion behavior, and the differentiating contrast with `save_market`, leaving nothing an agent needs in order to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description goes further by framing the intent of `add` (terms the corpus should hold) versus `remove` (terms that don't belong, and the persistent exclusion effect of removing a heuristic term), adding conceptual meaning on top of the already-detailed schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific paired verb (add / remove) operating on a single storefront's corpus, and explicitly names what it is not (`save_market`). An agent can distinguish it from the heuristic-rerunning seed tool without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use conditions: add for a term the corpus SHOULD hold but doesn't (rival brand, unmeasured phrase, thin storefront's real subject); remove for a term that does not belong, noting ANY term can go. It also contrasts with `save_market`, explaining that seeds re-run the heuristic while these writes act on single terms in the result.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

explain_periodExplain PeriodA
Read-only
Inspect

Return an account or owned-app summary for a requested period: available measured App Store Connect performance, storefront changes, tracked keyword movements, recorded signals, review volume and the last sent digest. Returns comparison data, coverage and caveats identifying unavailable sections. Reads account data without changing tracking or publishing content.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
app_idNoNarrow to one of the account's own apps — get_account lists them. Omit for the whole portfolio, which excludes apps the owner has hidden.
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `last_month`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling.
compareNoWhat to read the period against. `previous` (the default) is the period immediately before — for a calendar month, the calendar month before. `year_ago` is the same dates a year earlier, for comparison with the corresponding period in the previous year.
countryNoStorefront for the keyword half only; the money is reported across all of them with a per-storefront split. Defaults to the app's primary storefront. Ranks only exist per storefront.

Output Schema

ParametersJSON Schema
NameRequiredDescription
digestNo**What the customer was last mailed** — the weekly digest, sent Wednesdays, that leads with the measured week and at most three moves. Null when none has gone out yet. Anything here is breakfast reading, not news: say "as your digest said" rather than presenting it as a discovery, and lead with what happened since `sent_at`.
windowYesThe window actually read, which is not always the one asked for: an inverted range is swapped, a range past three years is shortened, and first-party figures are pulled back to the last day App Store Connect reported. Quote these dates, not the requested ones.
app_idsYesThe apps this covers.
caveatsYesEverything the reader has to hear before this reads as a complete picture: apps with no connection, a window shortened to the last day Apple reported, a corpus larger than the movers shown. **Pass these on.** A composed answer that drops them is the confident-and-wrong failure this tool exists to avoid.
reviewsYesReview arrivals — a flow, counted over the period. Not the backlog of unanswered reviews, which has no window and is on the console's reviews screen.
signalsYesWhat the product itself detected. A stored record, not a reconstruction — quote these sentences rather than re-deriving them from the numbers.
headlineYesThe two or three sentences a person would open with, each carrying its own figures. Lead with these; everything below is the evidence for them.
keywordsNoThe keyword half. Null when the account tracks no apps.
performanceNoThe measured half. **Null when the account has no apps at all**, and present with null figures when it has apps but no App Store Connect connection — those are different answers, and the caveats say which.
comparison_windowNoWhat everything here is read against. Null when `compare` was `none`.

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false, openWorldHint=false, so the safety profile is covered. The description still adds real behavioral context beyond that: output includes comparison data, coverage and caveats identifying unavailable sections, and it explicitly states reads do not change tracking or publishing content.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with what is returned, then a second sentence on return shape and read-only behavior. The enumerated section list is long but each item earns its place by defining the aggregate; no filler or throat-clearing.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description needn't detail return values, yet it usefully flags caveats and unavailable-section coverage. Safety is covered by annotations and all six parameters are fully documented in the schema, leaving only the missing when-to-use/routing guidance as a gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3; the schema already explains default windows, from/to precedence, app_id scoping, and the compare enum. The description adds little parameter-level meaning beyond restating that it covers a requested period and noting per-storefront money reporting (which the schema attributes to country).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb (Return) on a specific resource (account or owned-app summary) scoped to a requested period, with the aggregated sections enumerated: performance, storefront changes, keyword movements, signals, review volume, last digest. The aggregate role clearly separates it from single-purpose siblings like get_performance, get_signals, and get_reviews without needing to name them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit when-to-use statement, no exclusions, and no routing to alternatives such as get_performance or get_reviews when only one section is wanted. Usage is only implied by the aggregate framing plus the schema's detailed parameter guidance, which the agent must infer triggers this tool rather than a narrower sibling.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_accountGet AccountA
Read-only
Inspect

The account: its apps with Apptail ids, its plan and the limits that can refuse a write, App Store Connect health per app, how fresh the first-party data is, and the storefronts it focuses on. Call this first whenever the request is about "my app" or "my keywords" — every other tool needs an app_id, and the focus countries and hidden apps here are what keep your answer agreeing with what the customer sees in the console.

ParametersJSON Schema
NameRequiredDescriptionDefault
include_hiddenNoAlso return apps the owner has set aside. Default true, because they are still tracked and still answerable — each one is marked `hidden: true`, and they must be left out of portfolio totals. Set false for just the working portfolio.
include_competitorsNoAlso return the rival apps tracked under this account's apps, each marked `is_competitor`. Default false: including them makes any portfolio total wrong, and get_competitors is the tool that answers questions about them.

Output Schema

ParametersJSON Schema
NameRequiredDescription
appsYesThe account's apps. `app_id` here is what every other tool takes; the numeric id in an App Store URL is `apple_app_id` and will not work.
planYesThe plan and its ceilings, read from the services that enforce them — so a refusal cannot disagree with this. Tell the user what was capped rather than working around a limit.
countYesNumber of apps returned.
connectionYesApp Store Connect health for the account. A `failed` or `stale` connection looks exactly like a quiet fortnight in the numbers, which is why it is stated separately rather than left to be inferred. `none` means Apple has never been connected — impressions, page views, downloads and proceeds do not exist for this account and never did, so say that rather than reporting a decline; everything AppTail crawls itself (ranks, charts, reviews, ratings, releases, prices, markets) works exactly as it does for anybody else. `pending` is the same absence with a connection already on its way: repeat the wait, not the setup instructions.
data_throughNoMost recent day App Store Connect reported anywhere in the account. Per-app dates are on each app, and they differ — one healthy app keeps this recent while another has been silent for weeks.
focus_countriesYesThe storefronts this account actually watches, in its own order. Every console screen narrows to these. Narrow your answers to them too unless the user asks otherwise — an answer across all 122 storefronts buries the one they wanted.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint/openWorldHint=false, so the safety profile is covered. The description adds real context beyond that: it discloses that plan limits can refuse a write, that first-party data has a freshness dimension, and that hidden apps must be excluded from portfolio totals to keep answers consistent with the customer's console.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the returned payload before the routing instruction. The first sentence is a dense enumeration but every item earns its place; no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-shape explanation is unnecessary, yet the description still covers payload scope, the must-call-first routing role, and the consistency caveats. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and both booleans are documented with defaults and rationale, so the schema carries the burden. The description mentions hidden apps and focus storefronts but adds no syntax, default, or format detail beyond what the schema already provides; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (get the account) and enumerates the payload: apps with Apptail ids, plan/limits, ASC health, data freshness, focus storefronts. It explicitly distinguishes itself from every sibling by noting that all other tools require an app_id obtained here.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit routing rule — 'Call this first whenever the request is about "my app" or "my keywords"' — plus the reason (every other tool needs an app_id). It also flags that focus countries and hidden apps must be honored for answers to match the console, which is actionable invocation guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_appGet AppA
Read-only
Inspect

Everything AppTail holds about one app, at the depth you ask for. The base answer is the store listing in one storefront — the localised title and subtitle a shopper in country actually reads, the price and its currency, version, category, release dates, the publisher's ids. include adds sections: prices (what it costs in every storefront), ratings (every storefront's rating and vote count, pooled honestly, plus a daily series and per-storefront movement with history_days), charts (the charts it currently ranks in), popularity (organic reach), developer (the publisher and its portfolio), versions (release cadence) and screenshots. Works for ANY app in the store, not only the user's own. It does NOT return impressions, downloads or revenue — get_performance does, and only for the user's own connected apps.

ParametersJSON Schema
NameRequiredDescriptionDefault
app_idYesApptail app id. get_account for the user's own apps, search_apps for any other app in the store. Not the numeric id in an App Store URL (that is apple_app_id).
countryNoStorefront to read the listing in (e.g. US, GB, DE). Defaults to the app's primary storefront. Title, subtitle, price, rating and screenshots all differ per storefront, so this changes the answer rather than filtering it — the `name` you get back is the title as published *there*, which for a localised app is not the title in any other market. `ratings.overall`, `ratings.by_country`, `ratings.history` and the `prices` section ignore it — they are every storefront — and `charts` is returned for all of them regardless.
includeNoExtra sections to read. Omit for the listing alone, which is the cheap answer. `ratings`, `charts` and `popularity` need a Starter or Pro plan and are refused by name without one — the rest are free. Ask for what the question needs and nothing else: each section is a query.
history_daysNoWith `include: ["ratings"]`, also return the rating and the vote count day by day, this many days back from today, **and how each storefront moved over that window** — `ratings_gained` and `rating_change` on every row of `ratings.by_country`. Capped at 365. Ask for it whenever the question is whether a rating is *moving* or *where* it is moving: an average slides for weeks before enough people write about it, and a cross-section cannot tell a market that has always been low from one that fell this month. Ignored without the `ratings` include.

Output Schema

ParametersJSON Schema
NameRequiredDescription
iconNoAbsolute URL of the app icon.
nameNoThe app's title **as published in `country`** — the string a shopper in that storefront reads, which is also the string Apple indexes. It is not a translation of one canonical name: an app localised for Mexico has a different title there, and `get_app(country: "mx")` returning the US one is what makes an agent report a localised app as "not localized". Falls back to the app's canonical name when this storefront has not been crawled yet, and is null only while a freshly imported app is still being processed.
tierNoApptail activity tier for `country`: 1 = currently charting, 2 = shipped more than two updates in the last 180 days.
priceNoDownload price in `country`, in that storefront's own currency and in major units — 9.99, never 999. 0 means free, which is what most apps are; `currency` names the money. Null when this storefront has no listing on file.
storeNoWhich store the app belongs to.
app_idYesApptail app ID. This is the `app_id` every other tool expects.
chartsNoCharts the app currently ranks in, across all countries and categories. Present only with `include: ["charts"]`; empty means it charts nowhere.
pricesNoWhat the app costs to download, per storefront. Present only with `include: ["prices"]`. This is the download price alone: `has_inapps` says whether there is more to pay after that, and get_app does not return in-app purchase tiers.
countryNoStorefront the metrics below were read from. Defaults to `primary_country`.
is_mineNoTrue when this is one of the asking account's own apps. Null when the call carried no account — get_top_charts answers without a token — which is "not known here", not "no".
ratingsNoPresent only when `ratings` is in `include` — a section you did not ask for is absent from the answer entirely, which is not the same as it being empty.
versionNoLatest published version string.
currencyNoISO code of the currency `price` is in, derived from `country`. A price without it is a number, not an amount — several storefronts print a "$" and mean different money.
includesYesThe sections actually read. A section absent from this list is absent from the answer because nobody asked for it — which is not the same as it being empty.
subtitleNoThe subtitle under the title in `country` — thirty characters Apple indexes as heavily as the title, and the field most competitors leave generic. Null when the storefront listing has none, which is itself worth saying: an empty subtitle is unspent keyword weight.
versionsNoRelease history. Present only with `include: ["versions"]`. Cadence is the one competitor signal readable straight off the store: four releases in six weeks then eight months of nothing is a team that moved on.
bundle_idNoStore bundle identifier, e.g. "com.burbn.instagram".
developerNoThe publisher and its portfolio — with the cap on `top_apps` declared. Present only with `include: ["developer"]`; null there when AppTail holds no profile for the publisher.
has_inappsNoWhether the app offers in-app purchases.
popularityNoOrganic reach in `country`. Present only with `include: ["popularity"]`, and **null there when no storefront could be resolved** — a key that is present and null means "asked for, nothing to read", which an absent key does not.
seller_urlNoPublisher's marketing website.
console_urlNoWhere to open this app in the AppTail console — the owner's own dashboard when `is_mine`, the research view otherwise. Null where this environment serves no console. End an answer about an app with it.
screenshotsNoPresent only with `include: ["screenshots"]`, and null there when no storefront could be resolved.
support_urlNoSupport page URL.
apple_app_idNoApple's own numeric app ID, as it appears in App Store URLs. Not interchangeable with `app_id`.
developer_idNoApptail developer ID. Pass this as `developer_id` to get_developer_info. Null if we hold no profile for the publisher.
rating_countNoNumber of ratings in `country`.
release_dateNoDate the app first appeared in the store (YYYY-MM-DD).
rating_averageNoMean star rating (1-5) in `country`. Null if the app has no ratings there yet.
primary_countryNoLowercase ISO country code of the app's main storefront, e.g. "us".
primary_categoryNoStore category ID. Pass this as `category` to get_top_charts.
privacy_policy_urlNoPrivacy policy URL.
store_developer_idNoThe store's own publisher ID, as it appears in store URLs. Not interchangeable with `developer_id`.
listing_storefrontsNoOne storefront per language the listing is localized in, `primary_country` first, e.g. ["ru", "us"]. An app published in two languages is searched for in two storefronts; keyword discovery and tracking are per storefront, so cover each of these rather than only `primary_country`.
is_tracked_competitorNoTrue when the account already tracks this app as a competitor of one of theirs. False on a high-ranking rival is a suggestion worth making: add_competitors starts watching it. Null when the call carried no account.
primary_category_nameNoHuman-readable name of `primary_category`, e.g. "Health Fitness".
current_version_release_dateNoDate the current version shipped (YYYY-MM-DD). Useful as an update-cadence signal.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Offers substantial disclosure beyond the readOnly/openWorld/destructive annotations: plan gating ('ratings, charts and popularity need a Starter or Pro plan and are refused by name without one'), cost behavior ('each section is a query'), a hard cap (history_days capped at 365), and an explicit non-goal (no impressions, downloads or revenue). This is exactly the extra context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core answer, then layers includes, then exclusions and the alternative tool. Dense but every sentence carries routing, cost or scope information. Length is justified by a 4-parameter tool with a 7-value enum, though a few phrases ('pooled honestly') are decorative rather than informative.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, return fields need not be explained, and the description fully covers what an agent needs: scope, storefront semantics, section gating, plan requirements, cost, caps, and the excluded performance metrics. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds narrative meaning: `country` 'changes the answer rather than filtering it', and sections are grouped by what question they answer (e.g. `popularity` = organic reach, `versions` = release cadence). Some content overlaps the already-rich schema, preventing a 5, but it does more than echo structured fields.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Everything AppTail holds about one app') with an explicit scope boundary: the base answer is 'the store listing in one storefront', and `include` adds named sections. It also explicitly distinguishes itself from get_performance and clarifies it works for 'ANY app in the store, not only the user's own', which separates it from the user-scoped siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use routing: get_account for the user's own apps, search_apps for any other app, get_performance for impressions/downloads/revenue. It also states when to use each include section and, crucially, when to omit them ('omit for the listing alone, which is the cheap answer'), plus which sections need a paid plan.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_competitorsGet CompetitorsAInspect

List tracked competitor apps for one of your apps, with how many more the plan allows — and suggested: up to ten rivals the account does not track yet, ranked by how many of the app's own search terms they sit in the top ten for. Works on an app with no keywords: its listing is read for terms and the store is crawled for them. Read suggested_basis.terms_pending before calling a short list complete. Suggestions can persist shared keyword records and queue background crawls. Suggested apps are not added to your competitor watchlist.

ParametersJSON Schema
NameRequiredDescriptionDefault
app_idYesApptail app id for one of YOUR apps — get it from get_account. Not the numeric id in an App Store URL (that is apple_app_id).

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYesNumber of competitors returned.
limitYesHow many competitors this plan allows per app. -1 means unlimited.
app_idYesThe app these rivals are tracked against.
remainingNoSlots left before add_competitors is refused. Null when the plan is unlimited.
suggestedYesRivals worth tracking that are not on the list, strongest evidence first: apps in the top ten of the terms this app competes on (tracked, already ranked for, or read off its listing when it has neither), never the account's own apps, never one already tracked, never one hidden from the Landscape. Offer them with their `sample_terms`; add with add_competitors only when asked. Empty when the app has nothing to derive from — say that rather than "no competitors exist".
competitorsYesApps tracked as competitors of the given app. Empty if none are tracked yet.
suggested_basisNoWhat the suggestions were derived from. Null when the app is not one of the account's own — a suggestion needs your terms to read.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=false, the description proactively discloses the non-obvious write side effects: suggestions can persist shared keyword records and queue background crawls. It also explains the crawl/listing fallback for apps without keywords — real behavioral context beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose is front-loaded in the first clause, followed by the suggested-rivals explanation and the persistence/watchlist caveats. Dense but each sentence carries information; the opening sentence is long and stacks a secondary clause, costing a little clarity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still points at the specific field (suggested_basis.terms_pending) needed to interpret completeness, and covers side effects and watchlist behavior. Nothing essential to correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single app_id parameter is fully documented in the schema, including the get_account source and the apple_app_id distinction. The description adds no parameter-level detail, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (list tracked competitor apps) plus the secondary output (suggested untracked rivals) and the ranking basis. An agent can distinguish this from add_competitors/remove_competitors without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear operational context: it works on apps with no keywords, tells you to read suggested_basis.terms_pending before treating a short list as complete, and clarifies that suggestions are not added to the watchlist. It does not explicitly name alternative siblings or state when-not to call it, so not a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_keywordsGet KeywordsA
Read-only
Inspect

The account's tracked keywords for an app — or across the whole portfolio — over any window: where each term ranked at the start and end, how far it moved, how many days it held and how many days it was measured, and optionally its day-by-day history, the same terms measured for named competitors, Search Visibility (a 0–100 score per storefront with the terms that moved it), and the terms two of your own apps are both on. Sorted worst movement first by default, so "what dropped" is at the top of a 300-term corpus — which also makes the returned rows a selection rather than a sample: movers and coverage are counted over the whole corpus and are what a summary comes from. Filter by contains to ask about one term by name. This is the tool for every question about tracked terms; it replaces get_tracked_keywords, get_keyword_positions and compare_keyword_positions.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
sortNo`movement` (the default) puts terms that left the results first, then real drops by size — the top of the list is what to ask about. `rank` is best position first, `volume` is most searched (Apple popularity), `competition` is most crowded. **Every sort selects, so a capped list is never a sample**: a movement-sorted read of 15 terms returns 15 movers whatever the corpus did. Count from `movers` and `coverage`, never from the rows.
limitNoMax terms returned, after sorting. Default 100, capped at 300. The answer always says how many the corpus holds.
scopeNo`app` (the default) reads one app's corpus and needs `app_id`. `portfolio` reads every app the account owns and reports each term under whichever of them ranks best for it — that is the scope `include: ["shared"]` needs, and hidden apps are excluded from it.
app_idNoApptail app id for one of YOUR apps — get_account lists them. Required for `scope: "app"`. Not the numeric id in an App Store URL (that is apple_app_id).
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `30d`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling.
tag_idNoOnly keywords carrying this tag. Tag ids come back on the keywords in this tool's own output. Omit for the whole corpus.
countryNoStorefront to read ranks in (e.g. US, GB). Defaults to the app's primary storefront. Ranks only exist per storefront — the same term ranks differently in each — so one call answers for one market.
includeNoExtra blocks. `history` adds a day-by-day series per term, capped at 25 terms — pair it with `keyword_ids` for specific terms. `visibility` adds **Search Visibility** — the 0–100 answer to "is our ASO working here", its like-for-like change and the terms that moved it, plus a line per storefront — and each row's `points_change` and `brand`; also the daily top-N counts, which are over every term the app appeared for, not over this corpus. `shared` adds the terms two of your own apps are both on, and needs `scope: "portfolio"`.
containsNoOnly terms whose text carries this substring, matched case-insensitively and applied before the sort and the cap. This is how to ask about a term by name — "how is `mpg` doing" — without looking up its id first, and the only way to see a term that sits mid-table by movement and so never reaches the top of a sorted list.
keyword_idsNoOnly these terms, by Apptail keyword id — from this tool's own output or discover_keywords. Ids the account does not track are still answered when the scope is one app, because "how is my app doing for this term" is a real question; the answer says so. Omit for the whole corpus.
compare_withNoApptail app ids to measure on these same terms — from get_competitors or search_apps. Rivals are measured on YOUR corpus, not theirs. At most 5 are used and the answer names which; send the ones that matter first.

Output Schema

ParametersJSON Schema
NameRequiredDescription
sortYesThe order the list came back in.
countYesTerms returned.
scopeYesWhich scope answered: `app` or `portfolio`.
totalYesTerms the corpus holds in this storefront. Larger than `count` when the list was capped.
moversYesThe shape of the window in four numbers, **counted over every term in the corpus, not over the rows returned**. This is the only honest basis for a sentence about the corpus, and it is the one to lead with: 8 fell of 103 is a different week from 80 of 103, and a list of 15 rows cannot tell you which you are in.
sharedYesTerms two of the account's own apps are both on, worst first, when `include: ["shared"]` was asked for at portfolio scope. Empty otherwise. Read `competing` before calling anything a conflict.
windowYesThe window actually read, which is not always the one asked for: an inverted range is swapped, a range past three years is shortened, and first-party figures are pulled back to the last day App Store Connect reported. Quote these dates, not the requested ones.
app_idsYesThe apps these ranks were read for.
caveatsYesSentences the reader has to hear before the list means what it looks like it means — a capped list and what it was selected for, terms nobody crawled, ranks that are weeks old, a history returned for only some rows, an include that did not apply. They carry the corpus-wide numbers in them so they survive being quoted alone. Pass these on; do not summarise them away.
countryYesStorefront the ranks were measured in.
coverageYesHow much of this corpus was actually looked at in this window — the answer to "is this a drop or a gap in your crawling", which is otherwise unanswerable from ranks alone and has been guessed at wrongly. AppTail does not crawl every term every day, so sparse coverage on a few terms is normal; a corpus-wide `unmeasured` is not.
keywordsYesThe terms, in the order `sort` asked for. **A selection, not a sample**: this is the top of a sorted list, so when `truncated` is true these rows are whatever the sort favoured — with the default sort, the worst movers and nothing else. Read them for which terms to ask about next; read `movers` and `coverage` for anything about the corpus. Empty when none are tracked in this storefront — track some with add_keywords.
truncatedYesTrue when `count` is below `total`. Say the list is partial rather than reporting it as the whole corpus.
visibilityNoWhen `include: ["visibility"]` was asked for: Search Visibility (`score`, `storefronts`) and the daily band counts. **The band counts are not over this corpus**: they count every term the app appeared for that day in this storefront, tracked or not, so they move with whatever anybody crawled and can disagree with the rows (a corpus whose rows show 27 terms in the top 10 can read `top10: 20`). Read them as reach across the store, never as the corpus's state. Each is the last day with a row — a state, not a sum — and null when nothing has been rolled up. The whole block is null when visibility was not asked for.
compared_app_idsYesThe competitor ids actually measured. Shorter than what was sent when the cap or an unknown id dropped one.
compare_truncatedYesTrue when competitor ids were dropped for exceeding the cap of 5.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnlyHint=true, destructiveHint=false, openWorldHint=true), so the description focuses on behavior instead: results are sorted worst-movement-first, rows are a *selection* not a sample, and `movers`/`coverage` are counted over the whole corpus. That selection-vs-sample disclosure materially changes how an agent should interpret output. It stops short of covering pagination/rate-limit behavior, so it is strong but not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and the sibling-replacement statement are front-loaded, and nearly every sentence carries load-bearing detail (selection semantics, sort behavior, cap of 300). It is, however, a dense wall of prose with long em-dash-chained clauses that could be broken into scannable blocks, so it is efficient but not perfectly structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 13-parameter tool with an output schema present, the description covers the decision-relevant surface: scope/app_id coupling, include-block requirements, date-window resolution, and result-selection semantics. Return-value shape is left to the output schema, which is the correct division of labor.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description nevertheless adds interpretation beyond the schema, e.g. that `contains` is the only way to surface a mid-table term that sorting would never expose, and that `keyword_ids` still answers untracked terms under a single-app scope. These are meaning-level additions rather than restatements.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb+resource+scope ('the account's tracked keywords for an app — or across the whole portfolio — over any window') and enumerates exactly what each returned row contains (start/end rank, movement, days held, measured days). It explicitly names the three siblings it supersedes — get_tracked_keywords, get_keyword_positions, compare_keyword_positions — so an agent can distinguish it without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states outright that this is the tool for every question about tracked terms and lists the alternatives it replaces. It also gives conditional guidance inline: use `contains` to ask about one term by name, `scope: portfolio` for cross-app terms, `include: history` for day-by-day data. When-to-use and what-not-to-use are both explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_keyword_serpGet Keyword SerpAInspect

The App Store search results for one term in one storefront, on one day: who ranked, in what order, with their names, subtitles and ratings. This is how you find out WHO took the places an app lost — a rank that fell is a fact, and the results page on the day it fell is the reason. Works for ANY term in the store, tracked or not, known to us or not: pass keyword_id for a term you already have an id for, or keyword + country for words a user typed, which resolves the term and crawls the storefront live when what we hold is over a day old. Omit the date for the most recent crawl. A lookup can persist a new shared keyword record and queue a background crawl that updates stored search observations. It does not track the keyword for your account.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateNoThe day to read, `YYYY-MM-DD`. Omit for the most recent crawl, which is the only mode that ever crawls live — naming a day is a question about the past and is answered from what was recorded. A day nobody crawled comes back empty with `crawled: false` rather than as an error; pick one from `crawled_days`, which the answer always returns.
limitNoHow deep to read. Default 25, capped at 50. Below about 25 nobody is competing with you for the term.
countryNoStorefront for `keyword`, e.g. "us", "jp". Required with `keyword` and ignored with `keyword_id`, because a keyword already names its storefront. There is no worldwide search results page — every App Store search happens in one country.
keywordNoThe search term itself, for words a user typed rather than an id — "sleep tracker", "заметки". Needs `country`. A term nobody has ever looked up is created and the storefront is read live, so this answers for any search in the store and not only for terms Apptail already holds. Prefer `keyword_id` when you have one: it is the exact row, where words go through the store's own normalisation first.
keyword_idNoApptail keyword id — from get_keywords or discover_keywords, or off a `rank.move` signal. The storefront is fixed by the keyword itself; a term is tracked per country. Give this OR `keyword` + `country`, not both.

Output Schema

ParametersJSON Schema
NameRequiredDescription
appsYesThe results in rank order, best first. `is_mine` marks the account's own apps and `is_tracked_competitor` marks rivals it already watches — a high-ranking app that is neither is worth suggesting they add.
dateNoThe day actually read, which is the day asked for or the most recent crawl. Null when nothing has been crawled for this term.
countYesApps returned.
countryNoStorefront these results come from. Fixed by the keyword.
crawledYesWhether that day had a crawl at all. False with an empty list means nobody measured, **not** that nobody ranked.
keywordYesThe search term itself.
refreshedYesTrue when this call read the App Store rather than the database — a term looked up for the first time, or one whose last crawl was over a day old. False means these are stored results; `date` says which day they are from. Do not describe results as live unless this is true.
keyword_idYesThe term that was read.
popularityNoApple search popularity for the term, roughly 5-100. Higher means more searched. Read it before calling a position valuable.
console_urlNoWhere this search is shown in the console, with the icons and screenshots this payload leaves out.
my_positionNoThe best rank held here by one of the account's own apps, or null when none of them are in these results. Null is not -1: it means no app of theirs appears in the depth that was read, which is not the same as being measured and absent.
crawled_daysYesDays in the last 30 that were crawled, newest first. Name one in `date` to see how the results looked then — comparing two days is what shows who took a place.
results_countNoHow many apps the store returns for this term in total. The results below are the top of that.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses behavior well beyond annotations: a lookup can persist a new shared keyword record and queue a background crawl that updates stored observations, and can crawl the storefront live when cached data is over a day old. This reconciles the apparent read-only framing with readOnlyHint=false, and adds the empty-result semantics (`crawled: false`, not an error).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with what the tool returns, then input modes, then side effects. Some sentences are rhetorical rather than operational ('a rank that fell is a fact, and the results page on the day it fell is the reason'), but nearly every clause carries usable information for an agent.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return shape need not be explained. The description covers purpose, input mode selection, live-vs-recorded behavior, empty-day semantics, and the mutation side effect — complete for a 5-param open-world lookup.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter constraints the schema does not: keyword_id vs keyword+country are mutually exclusive, and country is ignored with keyword_id because the term already names its storefront.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+scope: 'The App Store search results for one term in one storefront, on one day: who ranked, in what order.' It is immediately distinguishable from siblings like get_keywords (tracked term metadata) and get_top_charts (chart rankings).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes between input modes ('pass `keyword_id` for a term you already have an id for, or `keyword` + `country` for words a user typed'), tells when to omit the date, and names the alternative sources of a keyword_id (get_keywords, discover_keywords). It also states a negative: 'It does not track the keyword for your account.'

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_landscapeGet LandscapeA
Read-only
Inspect

Who the App Store shows beside one of your apps for the terms it competes on, in one storefront: every app in the niche with how many of your terms it ranks for and owns the top 10 for, its ratings, its review flow and its size, plus which apps arrived or fell out this window — and term_gap, how much of the niche's own vocabulary you do not track yet, with the biggest holes ready for add_keywords. This is the niche, not your watchlist: most rows are apps nobody added, and get_competitors answers the other question.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
app_idYesApptail app id for one of YOUR apps — get it from get_account. Not the numeric id in an App Store URL (that is apple_app_id).
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `30d`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling.
countryNoStorefront to read the niche in (e.g. US, GB). A niche exists per storefront and is never blended across them. Defaults to the first of the account's focus_countries this app is actually tracked in, then the app's primary storefront.

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYesMembers returned.
termsYesHow many terms defined this niche: the account's tracked terms for this app in this storefront, plus the terms the app already holds a top-10 position for. Every `terms` and `top10_terms` figure below is out of this.
totalYesMembers the niche actually holds. Larger than `count` means the list was cut.
app_idYesThe app whose niche this is.
windowYesThe window used, and what the change figures are measured against.
caveatsYesSentences the reader has to hear before the numbers mean what they look like they mean — a cut list, hidden rows, an unmeasurable gap, modelled sizes. Pass these on; do not summarise them away.
countryYesThe storefront actually read, which may not be the one asked for.
membersYesThe niche, most of the account's terms owned first. `entered` and `left` mark this window's movers.
app_nameNoIts name.
term_gapYesHow much of the niche's vocabulary the account is not watching.
truncatedYesTrue when `count` is less than `total`. Say the list is partial rather than presenting it as the whole niche.
visibilityNoThe basis of each member's `visibility`. Null when it could not be read just now — then every member's `visibility` is null too, which is unknown, not zero.
console_urlNoThis landscape in the console, in this storefront.
hidden_countYesApps the account owner has hidden from this landscape in the console. They are excluded from `members`, from the movers and from `term_gap` alike.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, openWorldHint=true and destructiveHint=false, so the safety profile is covered. The description adds real behavioral context on top: the niche is storefront-scoped and not blended, rows include apps 'nobody added', and the window determines which apps 'arrived or fell out'. It omits any row limit or rate-limit note, which is the only gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the core purpose before the term_gap detail and the sibling routing. It is dense and em-dash-heavy, making it slightly hard to scan, but every clause carries distinct information and nothing is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be re-explained, and the description covers scope, sibling routing and follow-up actions. The only thing an agent might still wonder — how many rows come back or whether results are capped — is left unstated, keeping this just short of full completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so every parameter (to, from, app_id, period, country) is already documented in the schema with formats, defaults and override rules. The description only implicitly touches country ('one storefront') and period ('this window'), adding no syntax or meaning beyond what the schema provides. Baseline 3 is correct when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific resource (the niche landscape around one app in one storefront) and enumerates exactly what comes back: per-app ranking counts, top-10 ownership, ratings, review flow, size, arrivals/departures, plus term_gap. It explicitly separates itself from get_competitors by stating 'This is the niche, not your watchlist,' so an agent can distinguish the two without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It names the alternative (get_competitors) and the condition that selects it ('answers the other question'), and routes the follow-up work to add_keywords for the biggest term_gap holes. It also states the scope constraint that governs use — one storefront, never blended — which tells the agent this is a per-market read.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_marketGet MarketAInspect

Read iOS App Store niche data by saved market_id or by query and countries. Returns storefront competition, search visibility, app membership and optional corpus or movement data. Phrase research can create shared keyword records and persist crawled search observations; it does not save an account market. Downloads and revenue are labelled third-party rolling 30-day estimates. Reports per-storefront values, coverage and caveats.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
queryNoA phrase to resolve into a market right now, e.g. "AI calorie tracking". Requires `countries`. Can persist shared keywords and search observations. No market is saved to your account — call save_market with the same phrase to keep it.
storeNoRead only this one storefront of a saved market. Omit for every storefront compared, which is where `overlap` and the contest range come from.
app_idNoScopes `include: ["app_terms"]` to one app — an Apptail app id from `members`, `search_apps` or `get_landscape`. This is the audit trail for membership: it returns every (term, storefront) place that app holds in this market, how many days it held each, and which clause of the rule put it in `apps` or in `fringe`. Reach for it when the user disagrees with a market's membership, or asks why a particular app is or is not in the niche.
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `30d`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling.
includeNoExtra sections, each costing a query: `members` (the leaderboard), `corpus` (every term with why it is in), `movement` (who arrived and who left), `app_terms` (which terms ONE app holds here and whether that makes it a competitor — needs `app_id`). Ask for what the question needs.
countriesNoStorefronts to ask the question in, ISO 3166-1 alpha-2 (e.g. ["US","GB","DE"]). Required with `query`. Each storefront gets its own corpus seeded from the same phrase; that is deliberate, and a phrase nobody searches in a storefront comes back with a thin corpus, which is itself the finding.
market_idNoApptail market id for a market this account has saved — from list_markets. A saved market is a pure read: no crawling, sub-second. Send this OR `query`, not both.
budget_secondsNoHow long a live `query` may spend crawling, shared across ALL storefronts rather than each. Default 5, max 30. It is rarely the binding limit — at most 12 terms are crawled per call whatever the clock says.
new_within_daysNoKeep only apps FIRST RELEASED inside this many days — "what has launched into this niche lately". Applied to every app ranking for the corpus before the list is cut, so these are the new apps in the market and not the new apps among its leaders; a young app is rarely a leader yet, so filtering the leaderboard itself would answer nothing. Affects `members` only. `share` stays a share of the WHOLE market, which is the point of it. Not the same question as `movement`: that is who started ranking here, which is mostly apps that are not new at all.

Output Schema

ParametersJSON Schema
NameRequiredDescription
appsYesUNION: apps COMPETING in at least one storefront, counted ONCE however many they rank in. Competing means a top-10 place on at least one of this market's terms AND a top-50 place on at least two of them — one term is a coincidence and a place nobody scrolls to is not competition. Never the sum of the per-storefront counts: one app in three storefronts is one app. A FLOOR rather than a count when `apps_capped` is true. Everything else the store returned is in `fringe`.
leftNoApps that fell out of every storefront they held. include: ["movement"].
nameNoWhat the market is called. Null for an unsaved reading.
slugNoIts address in the console.
termsYesSUM of the per-storefront corpora. The corpora are disjoint by construction — a term belongs to one storefront — so nothing is counted twice, and this is exactly what the market costs to crawl.
corpusNoEvery term, with its storefront and why it is in the corpus. Present only with include: ["corpus"].
fringeYesApps that rank somewhere in this market's search results and are NOT competing in it: one term only, or no top-10 place anywhere. Typically several times `apps` — measured on a nine-storefront market, 578 competing against 2,001 fringe. They are real placements and worth naming as context (a store that seats an app beside yours), never as rivals. Counted once and never double-counted with `apps`: an app competing in one storefront and fringe in another is competing.
spreadNoopen | contested | locked | mixed. `mixed` is the finding rather than a fudge: a niche locked in the US and open in Germany is the answer to "where should I launch this first". Storefronts in the same band report that band.
statusYesdraft | building | ready | failed. A market is `building` while ANY of its storefronts is — never report it ready because most of them landed. `ready` beats `failed`, so a market can read `ready` and still hold a storefront whose build died: check each storefront's `failed_at` before quoting its figures.
windowYesThe window read, and what `newcomers`, `departures` and the change figures are measured against.
caveatsYesSentences the reader has to hear before the numbers mean what they look like they mean. Pass them on; do not summarise them away.
enteredNoApps that arrived somewhere in this market this window. include: ["movement"].
membersNoThe leaderboard, ordered by rating count — the one size figure we measure for every app. Present only with include: ["members"].
overlapNoOf the apps holding a top-10 place in ANY storefront, the share holding one in EVERY storefront, 0-100. High means one leaderboard travels and a newcomer meets it wherever they launch; low means each storefront is its own contest, which is where an unowned storefront hides. NULL for a single-storefront market — it is not a question one place can answer.
built_atNoThe OLDEST of the storefronts' build times: a market is only as fresh as its worst.
app_termsNoEvery (term, storefront) place that app holds here, best place first — the evidence `app_verdict` was taken over. Present only with include: ["app_terms"].
market_idNoApptail market id, or NULL when this reading was resolved from a phrase and not saved. Pass it to save_market to keep it, or to get_market to read it again for free.
newcomersNoDistinct apps that arrived in at least one storefront this window. Arriving in `de` while already ranking in `us` counts as arriving in this market. **NULL means no storefront had a previous window to compare against** — nothing of this corpus was crawled then, which is the normal state of a market built inside the window. Never read null as 0, and never report a new market's whole membership as newcomers.
truncatedYesTrue when any list above was cut. Say so rather than presenting a partial list as the whole thing.
departuresNoDistinct apps that fell out of every storefront they held. Null on the same terms as `newcomers`.
app_verdictNoWhether the app named by `app_id` is competing in this market, and which clause of the rule decided. Present only with include: ["app_terms"].
apps_cappedYesTrue when the census stopped at its depth before it ran out of apps. `apps`, `newcomers`, `departures`, `downloads_30d` and `revenue_30d` are then floors, because all of them are summed over the counted set. Say "at least" rather than reporting the number as a total.
console_urlNoThis market in the console.
contest_lowNoThe least contested storefront's share of top-10 slots held by its top three, 0-100.
revenue_30dNoESTIMATE, same basis as downloads_30d.
stale_termsYesCorpus terms across the market past their 72-hour refresh.
storefrontsYesOne entry per storefront, and the only level at which contest, demand and depth exist. Compare these rather than averaging them.
contest_highNoThe most contested storefront's. A RANGE, deliberately: there is no market-level contest to average.
corpus_totalNoTerms the market holds. Larger than the list means it was cut.
size_coveredYesHow many of the `apps` above carry an estimate at all, INCLUDING the ones the vendor would only bound as "under 1,000" / "under 5,000". Those contribute nothing to the sums, so a market with many small apps reports a size that is a floor. A total over 40 of 291 apps is a different statement from one over 280 — say which when quoting the size.
downloads_30dNoESTIMATE. A third-party model, worldwide and 30-day, summed over the UNION with each app counted once. Adding the per-storefront totals would count the same worldwide figure once per storefront and be wrong by roughly the storefront count.
members_totalNoMembers the market actually holds. Larger than the list means it was cut.
pending_termsYesTerms in the corpus that could not be crawled inside the budget. Their ranks are missing from this reading, so it is a floor. Saving the market and asking again returns a deeper answer.
app_terms_totalNoPlaces the app actually holds. Larger than the list means it was cut.
storefront_countYesHow many places this question is asked in.
pending_storefrontsYesStorefronts that ran out of budget before they were read at all. Name them; a partial answer that reads as complete is the failure this field exists to prevent.

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false and openWorldHint=true; the description goes further by disclosing the concrete side effects of phrase research (creating shared keyword records, persisting crawled search observations) and, critically, what it does NOT persist. It also flags data provenance ("third-party rolling 30-day estimates") and that coverage/caveats are reported, which an agent needs to interpret results honestly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences, front-loaded with purpose and invocation mode, then returns, then side effects, then data caveats — a logical order with no filler. Some sentences are dense, but each carries distinct information that is not repeated elsewhere in structured fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and 100% schema coverage across 11 parameters, the description need not enumerate returns or per-parameter syntax; it supplies the high-level shape plus the non-obvious side-effect and provenance context. What is missing is only peripheral (auth/permission expectations, rate-limit behavior), which the annotations and schema largely imply.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 11 parameters in exceptional detail (including the market_id-vs-query exclusivity and period semantics). The description restates the two modes and mentions optional corpus/movement data but adds no syntax or format detail beyond what the schema provides, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Read iOS App Store niche data") and names both invocation modes (saved market_id vs. query + countries). It explicitly distinguishes itself from the write sibling by noting it "does not save an account market," so an agent can tell it apart from save_market without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The two calling modes give implied usage context, and the caveat about not saving a market hints at save_market as the alternative. However, no sibling is named as an explicit alternative and there is no when-not-to-use guidance (e.g., vs. get_landscape or search_apps), so routing is left largely to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_performanceGet PerformanceA
Read-only
Inspect

Read measured App Store Connect impressions, store views, downloads, sales and proceeds for apps connected to the authenticated account. Supports an owned app or portfolio, documented time windows, period comparisons and breakdowns by storefront or traffic source. Returns the actual reporting window, connection coverage and caveats. Does not provide private analytics for apps outside the account.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
grainNoBucket size for the series. Omit and it is chosen to fit the window — a day for a month, a week for a year. A grain too fine or too coarse for the window is ignored rather than refused.
scopeNo`portfolio` (the default) sums every app the account owns; `app` reports one, and then `app_id` is required. Hidden apps are in the portfolio sum only if the account has not hidden them — they are excluded, matching what the console shows.
splitNoBreak the totals down. `country` gives one row per storefront Apple named; `source` gives Apple's own attribution — App Store search, browse, app and web referrers. `source` reports the attribution associated with measured downloads. Rows need not add up to the total; the caveats say so.
app_idNoApptail app id, required when `scope` is `app`. Must be one of the user's own apps — get_account lists them. Measured analytics require an authorised App Store Connect connection for the account.
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `30d`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling.
compareNoWhat to read the window against. `previous` (the default) is the period immediately before — for a calendar month that is the calendar month before, not the same number of days. `year_ago` is the same dates a year earlier, for comparison with the corresponding period in the previous year.
include_appsNoOn `portfolio` scope, also return one row per app so a portfolio move can be attributed to the app that caused it. Default false.

Output Schema

ParametersJSON Schema
NameRequiredDescription
appsYesOne row per app, when `include_apps` was set on a portfolio call. Empty otherwise.
grainYesBucket size the series came back at.
scopeYesWhich scope answered: `portfolio` or `app`.
splitYesWhich breakdown was returned: `none`, `country` or `source`.
seriesYesThe same metrics bucketed across the window, oldest first. Empty when nothing is connected.
totalsYesWindow totals, one entry per metric, each carrying where it came from and what it may be compared with.
windowYesThe window actually read, which is not always the one asked for: an inverted range is swapped, a range past three years is shortened, and first-party figures are pulled back to the last day App Store Connect reported. Quote these dates, not the requested ones.
app_idsYesThe apps behind these figures.
caveatsYesSentences the reader has to hear before the numbers mean what they look like they mean — apps left out, a window shortened, a breakdown that does not sum. Pass these on; do not summarise them away.
coverageYesWhat the totals do and do not cover. Quote this whenever `connected` is below `apps`.
truncatedYesTrue when `split_rows` is shorter than `split_total`. Say so rather than presenting the returned rows as the whole breakdown.
split_rowsYesThe breakdown, biggest by downloads first. Empty when `split` is `none`.
split_totalYesHow many rows the breakdown has in total, which can exceed the number returned.
data_throughNoLast day App Store Connect has reported across these apps. Apple lands analytics one to two days late, so this is usually a day or two behind today; a gap of a week or more is a broken connection rather than a lag.
requested_windowYesWhat the call asked for, when that differs from what was read. Compare with `window` before quoting a total as covering the period the user named.
comparison_windowNoThe window `previous` on each metric was measured over. Null when `compare` was `none`.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnlyHint, destructiveHint=false, openWorldHint=false), and the description adds real behavioral context beyond them: it returns the actual reporting window, connection coverage, and caveats, and it discloses that hidden apps are excluded from the portfolio sum to match the console. It does not discuss rate limits or auth requirements beyond connection mention.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences: what it reads, what it supports, what it returns, and what it excludes. Front-loaded with the resource set and free of filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 9-parameter read tool with an output schema, this covers scope, exclusions, comparison/split capabilities, and even return caveats. Nothing material to correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with unusually rich per-parameter documentation (grain fallback behavior, period/compare semantics, split row caveats), so the schema already carries the semantics. The description only gestures at 'documented time windows', 'period comparisons' and 'breakdowns by storefront or traffic source', which adds little beyond what the schema says.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (read) and concrete resource set (impressions, store views, downloads, sales, proceeds) scoped to App Store Connect apps on the authenticated account. It is clearly distinct from mutation siblings like add_app or reply_to_reviews, though it does not explicitly contrast itself with the closest read siblings (get_signals, get_app).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear context: owned app or portfolio, documented time windows, comparisons, and breakdowns, plus an explicit exclusion ('Does not provide private analytics for apps outside the account'). It points at get_account for obtaining app ids. It stops short of naming a specific alternative tool for other kinds of reporting.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_reviewsGet ReviewsA
Read-only
Inspect

Reviews for any app over a window, filtered by storefront, star rating, text, or whether the developer has replied — plus the shape of the period: how many arrived per bucket, what they averaged, and where the app's pooled store rating stood while they did. Reviews exist for every app whether or not App Store Connect is connected, because they are read from the public store. This is the tool for "what are people complaining about": filter it and read the reviews yourself rather than asking for a summary. Every review says whether it can be answered (can_reply) and where an existing reply stands (reply_state); reply_to_reviews is what answers them.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
limitNoMax reviews, newest first. Default 25, capped at 200. The answer always says how many matched.
app_idYesApptail app id. get_account for the user's own apps, search_apps for any other. Reviews are readable for any app in the store, including competitors.
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `30d`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling. **This tool also takes `all`**, which is the app's whole review history and the only period that reaches a backlog older than three years. Reviews are kept for the life of the app and the console's own reviews screen has no date bound at all, so the ceiling above is about first-party analytics and not about these rows. Send `all` for "what have we never answered" and for anything asking about a review from years ago; the trend then buckets by quarter or year.
ratingNoOnly these star ratings, e.g. `[1, 2]` for the critical ones. Omit for all five. The `summary` counts are deliberately NOT filtered by this, so a call asking only for one-stars still learns how many reviews the period actually held.
countryNoOnly reviews from this storefront (e.g. de). Omit for every storefront, which is usually right — one storefront's reviews are a thin sample. Send it when `ratings.by_country` on get_app has pointed at a market, which is the follow-up that finds out why it is low.
containsNoOnly reviews whose title or body contains this text, case-insensitively. Title as well as body, because a complaint is as often the headline as the paragraph. This is how you check a hypothesis — "crash", "subscription", "ads" — rather than reading everything.
unansweredNoOnly reviews the developer has never replied to. False or omitted for all of them. Combined with `rating: [1, 2]` this is the work queue the console leads with — and **a backlog has no window**, so send `period: "all"` when you mean all of it. `summary.unanswered` is the whole-history count either way, and a list that is smaller than it is a windowed list, not a shorter backlog.

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYesReviews returned.
totalYesReviews matching the filters in this window. Larger than `count` when the list was capped.
trendYesThe shape of the period. Compare `average_rating` with `store_rating` bucket by bucket: the first is the mood of what arrived, the second is the running total the store ranks on, and the gap between them is the finding.
app_idYesThe app the reviews belong to.
windowYesThe window actually read, which is not always the one asked for: an inverted range is swapped, a range past three years is shortened, and first-party figures are pulled back to the last day App Store Connect reported. Quote these dates, not the requested ones.
countryNoStorefront the list was narrowed to, or null for every storefront.
repliesYesWhether replying is available to this account at all. Read it together with `can_reply` on each review: this is about the account's key, that is about the individual review having an App Store Connect id. Both must be true.
reviewsYesThe matching reviews, newest first, in the language they were written in. Read them; do not ask for a summary you can produce better yourself.
summaryYesThe period in figures. `arrived*`, `critical*` and `average_rating*` are flows *in the window*; `all_time`, `unanswered` and `store_rating*` are the app's standing position and ignore it. Mixing the two is how "we got 12 reviews and have 400" becomes one number.
app_nameNoIts name.
filteredYesTrue when any of `country`, `rating`, `contains` or `unanswered` narrowed the list. Worth saying out loud: "no reviews" under a filter is not "no reviews".
truncatedYesTrue when `count` is below `total`. Say the list is a sample rather than presenting it as everything that was written.
console_urlNoThe app's Reviews screen in the console.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds meaningful context beyond the readOnly/openWorld annotations: reviews are read from the public store and exist for every app regardless of App Store Connect connection, each review carries can_reply and reply_state, and summary counts are deliberately not filtered by rating. Return value specifics are partly delegated to the output schema, so not a full 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then progressively deeper detail. Dense but each sentence carries routing or behavioral value; the period/can_reply/reply_state sentence and a few em-dash asides are slightly overstuffed, keeping it from a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a 9-parameter tool with a live output schema and readOnly annotations, the description covers everything an agent needs: scope, cross-tool routing, the all-period ceiling caveat, and the distinction between windowed lists and whole-history summary counts.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema itself is unusually rich, so the baseline is 3, but the description adds selection rationale beyond it: filters map to use cases ('check a hypothesis — crash, subscription, ads'), country is a follow-up to ratings.by_country on get_app, and unanswered + rating [1,2] is the work queue.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Reviews for any app over a window') plus the filterable dimensions and the aggregate shape returned. It distinguishes itself from siblings by naming get_app, search_apps, get_account and reply_to_reviews and clarifying the read-yourself framing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit routing: 'this is the tool for "what are people complaining about"... rather than asking for a summary', 'get_account for the user's own apps, search_apps for any other', and '`reply_to_reviews` is what answers them'. It also gives when-not guidance for the country filter (one storefront is a thin sample).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_signalsGet SignalsA
Read-only
Inspect

What actually happened, from the record the product keeps: rank moves on tracked terms, chart entries, review spikes, competitor releases and price changes, plus — for the user's own apps only — week-over-week traffic and conversion shifts, download collapses, new reviews and rating moves. Each carries the two numbers behind it and the sentence that states the finding. This is a stored table read, not a reconstruction, so it is both cheaper and more reliable than diffing rank histories yourself. Covers the user's own apps and the competitors tracked under them; a rival's finding never asks_action.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
limitNoMax findings, newest first. Default 50, capped at 200. The answer always says how many the window actually holds.
typesNoOnly these kinds of finding. Omit for all of them. An unknown name is refused rather than ignored, so a filtered answer is never returned as though it were unfiltered.
app_idNoNarrow to one of the user's own apps — get_account lists them. Findings about the competitors tracked under it are included, because a rival taking your places is something that happened to you. Omit for the whole portfolio.
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `30d`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling.
include_sentNoMark the findings that already went out in an alert digest the user has read. Default false. Set it whenever you are about to summarise a period for someone: without it you cannot tell news from something they read at breakfast, and repeating the second as the first is how an assistant stops being believed.
min_severityNoFloor on how loudly a finding speaks. Default `neutral`, which is everything. `warning` drops a rival shipping a point release, which is worth knowing and not worth acting on. It is NOT the same as "what needs my attention" — read `asks_action` on each finding for that: a competitor taking a run of 1–2★ reviews is a warning about somebody else's week and asks nothing of this user.

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYesFindings returned.
totalYesFindings the window holds in total. Larger than `count` when the list was capped.
digestNoWhat the product has already told this person, when `include_sent` was set. Null otherwise, which means "not asked" rather than "never mailed".
windowYesThe window actually read, which is not always the one asked for: an inverted range is swapped, a range past three years is shortened, and first-party figures are pulled back to the last day App Store Connect reported. Quote these dates, not the requested ones.
app_idsYesThe apps of the user's these findings hang under.
by_typeYesHow many of each kind the window holds — the shape of the period, counted over everything rather than over the capped list. A key is absent when there were none.
signalsYesThe findings, newest first. Each carries the sentence that states it and the two numbers behind it. Empty is a real answer: it means nothing crossed a detector's threshold, which is the only way a threshold means anything.
truncatedYesTrue when `count` is below `total`. Say the list is partial rather than presenting it as everything that happened.
by_severityYesThe severities among the findings returned. Counted over the returned rows, not the window, so it describes the list in hand.

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish readOnly/non-destructive/closed-world, so the bar is lower, yet the description adds real context: it is a stored table read (cheap, reliable), it covers the user's own apps plus competitors tracked under them, and a rival's finding never asks_action. It does not discuss rate limits or pagination beyond the schema-stated cap. Solid added value, not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The scope and the cost/reliability rationale are front-loaded, and the enumeration of finding types is dense but useful. A few sentences carry rhetorical flourish ('how an assistant stops being believed') that is informative but slightly padded for a tool description. Still efficient overall.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return-value detail is not required, and the description covers scope (own apps + tracked competitors), cost profile, filtering behavior and the meaning of severity vs asks_action. Nothing an agent needs to invoke it is missing, though it could be tighter about interaction with sibling tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so every one of the 8 parameters is already fully documented with defaults, formats and precedence rules. The description adds no per-parameter syntax or meaning beyond that, so the baseline 3 applies — the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states concretely what the tool returns: stored findings such as rank moves, chart entries, review spikes, competitor releases, price changes, plus own-app traffic/conversion shifts. That is far more specific than the tool name and lets an agent distinguish it from performance/history tooling. It does not, however, name a sibling tool it should be preferred over, so 4 rather than 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear selection rationale against a competing approach ('This is a stored table read, not a reconstruction, so it is both cheaper and more reliable than diffing rank histories yourself'). The include_sent parameter note also supplies conditional guidance ('Set it whenever you are about to summarise a period'). It stops short of explicit when-not-to-use or sibling routing, so not a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_top_chartsGet Top ChartsC
Read-only
Inspect

Get top app charts (free, paid, or grossing) for a store, country, and category.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow deep to read the chart. Default 25, capped at 100.
storeNoOnly the App Store is covered. Google Play is in the data model but nothing is crawled for it, so omit this.
countryNoStorefront to read the chart for (e.g. US, GB, JP). Defaults to US. An unrecognised code falls back to US rather than failing — the reply echoes the storefront actually used, so check it.
categoryNoApple category id as a number, e.g. 6014 Games, 6015 Finance, 6017 Education, 6023 Food & Drink, 6013 Health & Fitness, 6012 Lifestyle. Omit for the overall chart across all categories. An id we do not recognise is ignored and you get the overall chart.
chart_typeNoWhich chart. Default free. Use grossing for revenue questions — top free ranks by downloads and says nothing about money. An unrecognised value falls back to free and the reply says which chart it read.

Output Schema

ParametersJSON Schema
NameRequiredDescription
appsYesChart entries, rank 1 first. Empty when no chart data exists for this country/category combination.
countYesNumber of entries returned.
countryYesStorefront the chart was read from.
chart_typeYesWhich chart was read.

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, openWorldHint=true, destructiveHint=false, so safety is covered. The description itself adds nothing behavioral — the genuinely useful traits (fallback to US, unrecognised values silently ignored, echoed storefront) live in the schema, not here.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no waste. It is tight, though arguably too terse for a five-parameter tool with non-obvious fallback semantics.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need not be explained, and annotations cover the safety profile. The remaining gap is usage/routing guidance, which the description does not supply for a tool with many overlapping read siblings.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and each parameter is documented in detail, including defaults, caps, and fallback behavior. The description merely names store/country/category without adding syntax or format meaning beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clear verb+resource: 'Get top app charts' with the chart-type dimension spelled out. It does not distinguish itself from siblings like get_landscape or get_performance, which an agent might reasonably confuse for chart/ranking data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use guidance, no prerequisites, no named alternatives. An agent is left to infer that this is the tool for store ranking questions rather than, say, get_landscape or get_app.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_marketsList MarketsA
Read-only
Inspect

List markets saved by the authenticated account, including market ids, storefronts, competition ranges, app counts and observed changes for the requested period. Reads saved account configuration and market observations without creating, updating or deleting markets. Returned ids can be used with get_market.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date, `YYYY-MM-DD`, inclusive. Send it alone and the window is the three years before it.
fromNoStart date, `YYYY-MM-DD`, inclusive. Overrides `period` when both are sent. Send it alone and the window runs to today.
periodNoThe window to report on. A calendar month as `2026-07`, a calendar year as `2026`, `last_month`, `this_month`, `last_year`, or a trailing window: `today`, `7d`, `14d`, `30d`, `90d`, `180d`, `365d`, `730d`, `1095d`. Defaults to `30d`. A calendar period is compared against the calendar period before it — July against June, not against the 31 days ending 30 June. Three years is the ceiling.

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYesMarkets this account keeps.
limitYesHow many its plan allows. Equal to `count` means the next save is refused; say so before the user tries.
windowYesThe window every change figure below is measured over.
marketsYesOne entry per market, newest first. Pass a `market_id` to get_market with include: ["members"] for the leaderboard behind any of them.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, openWorldHint=false and destructiveHint=false, so the safety profile is covered. The description nevertheless adds real context by specifying what is read ('saved account configuration and market observations') and explicitly ruling out create/update/delete, which is more than the annotations alone convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three lean sentences, front-loaded with what is listed and followed by the read-only guarantee and the id hand-off. The enumerated field list is slightly redundant given an output schema exists, but nothing is padded or buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a low-risk read tool with annotations covering safety and an output schema covering return shape, the description is nearly complete. The remaining gap is operational detail such as pagination, ordering, or behavior when the account has no saved markets.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with detailed per-parameter text for to, from and period including defaults and precedence rules, so the schema carries the parameter burden. The description only vaguely alludes to 'the requested period' and adds no syntax or precedence detail beyond the schema, which is the correct baseline of 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List markets saved by the authenticated account') plus the scope of what is returned, so the agent knows exactly what this produces. It also implicitly distinguishes itself from the singular sibling get_market by noting the returned ids feed into it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is clear: call this to enumerate saved markets for a reporting window, and the note that returned ids work with get_market routes the agent to the follow-up call. There is no explicit statement of when not to use it (e.g., versus get_landscape or get_performance for aggregated metrics), so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_competitorsRemove CompetitorsA
DestructiveIdempotent
Inspect

Remove one or more competitor tracking relationships for an app owned by the authenticated account. Accepts AppTail app ids, App Store URLs or names. Name and URL resolution can query the public App Store and import shared app records. Removes the selected pairing only; public apps and pairings for other apps remain. Requires write authorisation and returns one outcome per item.

ParametersJSON Schema
NameRequiredDescriptionDefault
app_idYesApptail app id for one of YOUR apps — from get_account. Only this app's competitor set is touched.
competitorsYesThe rivals to stop tracking, up to 20. Each item is an Apptail app_id (as returned by get_competitors), a store URL, or a name. Ids are the reliable form here — a name is resolved against the whole store, not against what you track.

Output Schema

ParametersJSON Schema
NameRequiredDescription
app_idYesThe app these rivals were tracked against.
outcomesYesOne row per item. `not_tracked` means it was not a competitor of this app to begin with — the end state is what was asked for, but do not report it as a removal.
remainingYesHow many competitors this app still tracks.
removed_countYesHow many pairings were actually removed.
requested_countYesHow many items were sent.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Going beyond the annotations, it discloses that name/URL resolution may query the public App Store and import shared app records (a real side effect), that write authorisation is required, and that one outcome is returned per item. These add genuine behavioral context, though reversibility of the deletion is not addressed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The action is front-loaded in the first sentence, followed by input forms, side effects, scope, and auth requirements in a compact five-sentence block. Every sentence carries information, though there is minor overlap between the body and the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive, open-world mutation with an output schema present, the description covers the key agent concerns: scope, side effects, auth, and per-item results. Return-value details are correctly left to the output schema, so the only minor gap is the lack of note on how failures per item are surfaced.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters thoroughly, including the id/URL/name forms and the 'up to 20' limit. The description largely repeats the accepted input forms rather than adding new meaning, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (remove), resource (competitor tracking relationships), and scope (for an app owned by the authenticated account). An agent can immediately distinguish this from add_competitors and get_competitors without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clarifies that only the selected pairing is removed and that public apps and other apps' pairings remain, which tells the agent what this tool will and won't affect. It does not explicitly name sibling alternatives like add_competitors, but the usage context is clear enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_keywordsRemove KeywordsA
DestructiveIdempotent
Inspect

Remove tracked keywords from an app. Frees the slots against the plan limit; position history is kept, so re-adding a term later does not start from nothing.

ParametersJSON Schema
NameRequiredDescriptionDefault
app_idYesApptail app id for one of YOUR apps — get it from get_account. Not the numeric id in an App Store URL (that is apple_app_id).
keyword_idsYesApptail keyword ids to stop tracking, as returned by get_keywords. Ids, not terms — two apps can track the same word and only these rows are removed. Frees the slots against your plan limit. Position history is kept, so re-adding a term later does not start from nothing.

Output Schema

ParametersJSON Schema
NameRequiredDescription
app_idYesThe app these terms were tracked for.
remainingYesHow many keywords this app still tracks, across every storefront.
removed_countYesHow many keywords were actually untracked. Lower than `requested_count` when some IDs were not tracked for this app.
requested_countYesHow many keyword IDs were passed in.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and idempotentHint=true, so the safety profile is covered. The description adds genuinely useful context beyond them: removal frees plan slots and position history is preserved, so re-adding does not reset progress. It stops short of stating auth requirements or that removal is immediate/permanent for the tracking row.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with zero waste, front-loading the action and resource before the consequence. Every clause carries information about effect or data retention.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive two-param tool with full schema coverage, annotations, and an output schema, the description supplies the key behavioral facts an agent needs (slot freeing, history retention). It is essentially complete, though a note on whether the action can be undone server-side would close the last gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both parameters are already fully documented, including the app_id vs apple_app_id distinction and the ids-not-terms caveat. The description merely echoes the keyword_ids semantics, adding no meaning beyond the schema; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Remove') and resource ('tracked keywords') scoped to an app, which cleanly separates it from siblings like remove_competitors, remove_market, and add_keywords. An agent can identify the tool's function without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the effect statement ('frees the slots against the plan limit'), but there is no explicit when-to-use guidance, no mention of prerequisites (e.g., needing ids from get_keywords), and no comparison to alternatives such as tag_keywords for organizing rather than removing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_marketRemove MarketA
Destructive
Inspect

Delete a saved market, or just one of its storefronts. Pass store to drop a single storefront and leave the rest of the market with its corpora and its build clocks untouched; omit it to delete the whole market. Deleting frees a slot on the account's plan. The terms themselves are not deleted — they are shared with any other market or tracked app using them.

ParametersJSON Schema
NameRequiredDescriptionDefault
storeNoDrop only this storefront, ISO 3166-1 alpha-2. Omit to delete the whole market. Dropping the last storefront deletes the market too, because a market asked nowhere is not a market.
market_idYesApptail market id — from list_markets.

Output Schema

ParametersJSON Schema
NameRequiredDescription
nameYes
deletedYesTrue when the whole market is gone. False when only a storefront was dropped and the market remains.
market_idYes
markets_remainingYesMarkets left on the account after this call.
removed_storefrontsYesWhat this call removed.
remaining_storefrontsYesWhat the market is still asked in.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the destructiveHint=true annotation by disclosing that deletion frees a plan slot, that shared terms survive deletion, and that dropping a single storefront preserves the rest of the market's corpora and build clocks. This is exactly the kind of side-effect and shared-resource context an agent needs before a destructive call.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with the dual behavior, then consequences. Every sentence carries distinct information (mode selection, plan-slot effect, term sharing).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described. Combined with destructiveHint annotations, the description covers mode selection, side effects, shared-resource caveats, and plan implications – everything needed to invoke safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both params (baseline 3). The description adds meaning beyond the schema by explaining the storefront-level outcome of `store` (rest of market and build clocks untouched), which the schema does not fully spell out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (delete a saved market or one storefront) and clearly distinguishes the two modes of operation. An agent can tell it apart from sibling tools like save_market, get_market, and list_markets without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to pass `store` versus omitting it, and what each choice leaves untouched. It doesn't name sibling alternatives (e.g., save_market or remove_keywords) as routing options, so it stops short of full when/when-not guidance, but the operational context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

reply_to_reviewsReply To ReviewsA
Destructive
Inspect

Create, update or remove public App Store review responses for apps owned by the authenticated account, using its connected App Store Connect API key. Requires write authorisation and eligible review ids. Submitted text is sent verbatim under the developer's name. Obtain approval for the exact text or requested removal before submitting. dry_run validates without publication. Accepts up to ten items and returns per-item outcomes; pending means Apple has accepted a response but has not published it.

ParametersJSON Schema
NameRequiredDescriptionDefault
dry_runNoCheck every item and report what would happen, without publishing anything. **Worth using by default here** — it confirms the reviews resolve, the account's key reaches their apps and the text fits, before anything reaches the store.
repliesYesThe replies, up to 10 per call. Send them in one call rather than one at a time: each is answered separately, so a batch where one app is not covered still posts the rest and says which it did not.

Output Schema

ParametersJSON Schema
NameRequiredDescription
dry_runYesTrue when nothing was sent to Apple. Every outcome is then what *would* have happened.
outcomesYesOne row per item, in the order they were sent. Read `status` per row, then `state` on the ones that posted.
max_itemsYesThe per-call cap. A request above it is refused as a whole rather than silently trimmed.
failed_countYesHow many did not do what was asked. Never report a batch as done while this is above zero — say which ones and why.
posted_countYesHow many replies Apple took. Read each one's `state` before calling any of them published.
removed_countYesHow many replies were taken down.
requested_countYesHow many items were sent.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (destructiveHint, openWorldHint), the description adds substantial operational context: text is published verbatim under the developer's name, removal is publicly visible once published, calls are batched up to ten items, per-item outcomes are returned, and a 'pending' result means Apple accepted but has not yet published.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is front-loaded with the action and scope, then moves to prerequisites, then to behavioural details. It is dense but every sentence carries weight; the only mild redundancy is restating the ten-item limit that the schema also documents.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive, multi-item mutation tool with strong annotations and a dedicated output schema, the description covers the remaining burden: approval prerequisites, dry_run semantics, partial-batch failure behaviour, and the pending outcome. An agent has everything needed to invoke it safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents dry_run, body, remove, review_id and the batching limit in detail. The description largely restates that information (up to ten items, dry_run validates without publishing) and adds little the schema does not already say, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names a specific verb set (create, update, remove) and a specific resource (public App Store review responses) with scope (apps owned by the authenticated account). It is immediately distinguishable from the read-only sibling get_reviews, and the auth mechanism (connected App Store Connect API key) is stated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear prerequisites (write authorisation, eligible review ids, approval of exact text before submitting) and recommends dry_run for validation. It does not explicitly route the agent to a sibling for obtaining review ids, though the schema's review_id description points at get_reviews, which covers the gap somewhat.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

save_marketSave MarketA
Destructive
Inspect

Create or update a saved market in the authenticated account from a query, countries and optional per-storefront seed terms. An existing name updates that market, adding storefronts or replacing seed configuration and rebuilding requested storefronts. Persists account configuration and queues background corpus collection using public App Store data. Requires write authorisation. Returns market_id, build status, affected storefronts and caveats; collection may still be pending.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoWhat to call it. Defaults to `query`. Passing the name of a market this account already has ADDS the new storefronts to it rather than creating a second one.
queryYesThe phrase to build the market from, e.g. "AI calorie tracking". Used as the seed in every storefront unless `seeds` overrides it.
seedsNoPer-storefront seed terms, keyed by country code: {"de": ["kalorienzähler"]}. OPTIONAL, and optional per storefront. A storefront you leave out is not seeded from `query`: its terms are read off the localised listings of the leaders of the storefront that WAS phrased, and each one is searched there before it is kept — so it ends up with the local phrasing rather than an English phrase nobody types. Pass a storefront explicitly when you know the local phrase (to use the supplied local terms) or when you want to ask a different question there. Up to five terms each.
countriesYesStorefronts to ask it in, ISO 3166-1 alpha-2 (e.g. ["US","DE"]). Each keeps its own corpus; nothing is blended across them.

Output Schema

ParametersJSON Schema
NameRequiredDescription
nameYes
slugYesIts address in the console.
statusYes`building` immediately after a save. A market is building while ANY storefront is; each one lands separately. A build that dies leaves the storefront `failed` (or `ready` with `failed_at` set when a previous corpus survives) — it never stays `building` for ever.
caveatsYesWhat the caller needs to hear — that the build is not finished, or that a storefront was seeded from an English phrase.
createdYesTrue for a new market, false when storefronts were added to one that already existed.
market_idYesPass it to get_market to read the market once it has built.
console_urlNoThe market in the console.
storefrontsYesEvery storefront this market is now asked in, including ones it already had.
added_storefrontsYesThe ones this call added or rebuilt.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: it discloses that an existing name replaces seed configuration and rebuilds storefronts (matching destructiveHint=true), that write authorisation is required, that corpus collection is queued as a background job which may still be pending, and what the response contains.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the create/update purpose, then layers update semantics, side effects, auth and returns in dense but purposeful sentences with no filler. Slightly long, but every sentence carries distinct information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the mutation path, prerequisites (write auth), asynchronous side effects and caveats for a four-parameter tool with nested seeds. With an output schema present, its restatement of return fields is a bonus rather than a necessity, leaving nothing essential missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so name, query, seeds and countries are already fully documented in the schema; the description only restates the parameter list rather than adding format or constraint detail. Baseline 3 applies when the schema carries the semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb pair (create or update) and resource (saved market) plus the inputs it is built from, so an agent can distinguish it from siblings like get_market, edit_market_corpus and list_markets without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly explains the create-vs-update branch: an existing name updates that market, adds storefronts, or replaces seed configuration and rebuilds. It gives strong context for the primary path but never names alternative tools (e.g. edit_market_corpus) or an explicit when-not-to-use condition.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_appsSearch AppsAInspect

Search for iOS apps by name, bundle ID, or App Store URL. Supports all languages including Cyrillic, Chinese, etc. If the app is not in our database, it will be fetched from the App Store, persisted as a shared public app record, and queued for enrichment. This does not add the app to your account or competitor watchlist. Auto-detects the likely App Store region from the query language (e.g. Cyrillic → Russia, Chinese → China). To add what you find, add_competitors and add_app take the same name or URL directly — you do not have to look up an app_id first.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax results. Default 10, capped at 25.
queryYesApp name in any language, a bundle id (com.example.app), or a full App Store URL. Searches Apptail first and falls back to the App Store, importing anything it finds — so this returns a usable app_id even for an app we have never seen. This is how you turn a name a user typed into an app_id.
countryNoStorefront to search (e.g. "us", "ru"). Omit and it is guessed from the script the query is written in — Cyrillic implies ru, and so on. Pass it explicitly when the user names a market, because the guess is about language and not about where they sell.

Output Schema

ParametersJSON Schema
NameRequiredDescription
appsYesMatching apps, best match first. Empty when nothing was found.
hintNoPresent when nothing was found: suggests how to retry.
noteNoPresent when the results need a caveat, e.g. data still being fetched in the background.
countYesNumber of apps returned.
sourceYesWhere the results came from. "app_store" means they were just imported and are still being enriched.
countryNoStorefront that was searched. Null when answered from the local database, which is not country-specific.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnlyHint=false and openWorldHint=true; the description supplies the real behavioral story the agent needs: a miss triggers an App Store fetch, persistence of a shared public app record, and an enrichment queue. That explains why a 'search' is not read-only, which the annotations alone leave opaque.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Six sentences, front-loaded with purpose and searchable forms, then side effects, then routing. Every sentence carries information, though the closing note that no app_id lookup is needed slightly overlaps what the query parameter description already says.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an open-world, non-read-only lookup tool with 3 params, an output schema, and full schema coverage, the description covers the import side effects, region inference, and sibling routing. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: the region is auto-detected from the query script (Cyrillic→ru, Chinese→China) and it warns that the guess reflects language, not market, so country should be passed explicitly when a market is named. It goes beyond restating the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource ('Search for iOS apps') and spells out the accepted query forms: name, bundle ID, or App Store URL. It is clearly distinguishable from get_app, add_app, and other siblings by being the lookup/import entry point.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states what this does NOT do ('does not add the app to your account or competitor watchlist') and routes the agent to the correct alternatives, add_competitors and add_app, noting they accept the same name or URL. This is exactly the when/when-not/alternatives guidance the dimension asks for.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tag_keywordsTag KeywordsA
Destructive
Inspect

Put tracked keywords under a tag, in bulk — or clear their tag — and/or say whether they are brand terms for Search Visibility. The tag is named, not looked up: a name that does not exist yet is created. A keyword carries at most one tag, so assigning replaces whatever it had. brand is separate from tags: own (your app's name) and competitor (a rival's name) leave the terms out of the visibility score, none counts them whatever detection says, auto hands them back to detection. Send brand without tag to change only that — the tags stay as they are. This is the tool for organising a large corpus after reading it with get_keywords, which is drudgery in the console and one call here.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagNoThe tag name to put them under. Created if it does not exist. Omit — or send null — to clear the tag off these keywords instead.
brandNoWhether these terms are somebody's name, for Search Visibility (get_keywords `include: ["visibility"]`). `own` = your app's name, `competitor` = a rival's: both are listed and left out of the score, because you rank #1 for your own name whatever your ASO does. `none` = count them anyway. `auto` = let title-word detection decide again. Detection is shown on each row's `brand.reason`; override it when it is wrong (a transliterated rival name, a generic word it took for a name). Omit to leave the brand decision alone. Sent without `tag`, only the brand decision changes.
colorNoHex colour for a tag being created, e.g. "#2563eb". Ignored when the tag already exists. Omit and one is picked from the palette that the app is not already using.
app_idYesApptail app id for one of YOUR apps — from get_account. Tags belong to an app.
keyword_idsYesApptail keyword ids to tag, as returned by get_keywords. Ids, not terms. Up to 500 in one call. A keyword the app does not track is reported back rather than silently ignored.

Output Schema

ParametersJSON Schema
NameRequiredDescription
tagNoThe tag the keywords now carry. Null when the action was `cleared`.
tagsYesEvery tag this app now has, so a follow-up call does not have to guess at names.
brandNoThe brand decision applied (`own`, `competitor`, `none`, `auto`), or null when none was sent. Search Visibility recomputes on the next get_keywords read.
actionYes`assigned` when a tag was set, `cleared` when the tag was taken off, `untouched` when only `brand` was sent and the tags were left as they were.
app_idYesThe app whose corpus was changed.
tag_createdYesTrue when this call created the tag rather than reusing one. Worth mentioning: a typo makes a second tag rather than an error.
unknown_idsYesIds this app does not track, so nothing was changed for them. Empty on a clean run. Never present these as tagged.
changed_countYesHow many keywords had their tag changed. 0 on a brand-only call.
requested_countYesHow many keyword ids were sent.
brand_changed_countYesHow many keywords took the brand decision. 0 when `brand` was not sent.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and openWorldHint=false, and the description is consistent with them while adding real behavioural context: a non-existent tag name is created, a keyword holds at most one tag so assignment replaces the prior one, the 500-id batch limit, and that untracked keywords are reported back rather than silently dropped. That is substantive disclosure beyond the structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core action and the tag-vs-brand distinction before the detail, and every sentence carries operational information. It is somewhat long and partly restates the brand enum values already documented in the schema, which keeps it from a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need not be explained, and the description still covers the app scoping, tag auto-creation, batch limit, replacement semantics, and error reporting. Nothing an agent needs to invoke this destructive mutation correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% so the baseline is 3, but the description goes further by explaining cross-parameter semantics the schema does not: how tag and brand interact (brand alone leaves tags intact), and what omitting each field does (clear the tag / leave the brand decision alone). It adds meaning beyond the per-field descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (bulk-tag tracked keywords) plus the secondary operations (clear tag, set brand), and explicitly separates itself from the read-side sibling by saying it is for organising after reading with get_keywords. An agent can distinguish it from add_keywords/remove_keywords without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the alternative workflow (get_keywords for reading, this tool for bulk organisation) and gives conditional usage modes: omit tag to clear, send brand without tag to change only the brand decision, override auto detection when it is wrong. The when-to-use context is explicit rather than inferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updates
    • Changedget_keywords2 fields changed
      • addedOutput schema / properties / visibility / properties / overall / properties / compared
        Added value: +{
        +  "description": "The apps in `compare_with` across storefronts — the console's competitor table column. Your weights, never theirs: their own download split is not yours to read, and a different weighting would not compare. Empty when nothing was compared.",
        +  "items": {
        +    "properties": {
        +      "app_id": {
        +        "description": "Apptail id of the app from `compare_with`.",
        +        "type": "integer"
        +      },
        +      "band": {
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "change": {
        +        "description": "Like-for-like, as `change` above.",
        +        "type": [
        +          "number",
        +          "null"
        +        ]
        +      },
        +      "name": {
        +        "description": "Its name.",
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "previous": {
        +        "type": [
        +          "number",
        +          "null"
        +        ]
        +      },
        +      "score": {
        +        "description": "Its overall on YOUR non-brand terms, weighted by YOUR storefronts' `weight` — directly comparable with `score` above. 0 = nothing in the top 50 on them.",
        +        "type": [
        +          "number",
        +          "null"
        +        ]
        +      }
        +    },
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / visibility / properties / overall / required
        Previous value: -[
        -  "terms_scored",
        -  "storefronts_scored",
        -  "weights_source",
        -  "weights_month",
        -  "formula_version"
        -]New value: +[
        +  "terms_scored",
        +  "storefronts_scored",
        +  "weights_source",
        +  "weights_month",
        +  "compared",
        +  "formula_version"
        +]
    • Changedget_landscape2 fields changed
      • addedOutput schema / properties / members / items / properties / visibility
        Added value: +{
        +  "description": "Search Visibility, 0–100, on the account's **tracked non-brand terms** in this storefront: the same score get_keywords gives your app, computed for this one — 100 = #1 on every term, the mean of the last 7 days. **Not `share` rescaled, and not in its order**: `share` is over this landscape's wider corpus (it adds the terms the app ranks top 10 for untracked, and brand terms) at each app's best place in the window. 0 is a measurement — nothing in the top 50 on those terms. Null when the account's terms here cannot be scored or the read failed — top-level `visibility.reason` / `caveats` say which.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / visibility
        Added value: +{
        +  "description": "The basis of each member's `visibility`. Null when it could not be read just now — then every member's `visibility` is null too, which is unknown, not zero.",
        +  "properties": {
        +    "rank": {
        +      "description": "Your place among the apps scoring above zero on those terms. Null with no score, or at zero.",
        +      "type": [
        +        "integer",
        +        "null"
        +      ]
        +    },
        +    "ranked": {
        +      "description": "How many apps score above zero on those terms — the whole top 50, not only the listed members.",
        +      "type": "integer"
        +    },
        +    "reason": {
        +      "description": "Why there is no score, with the fix.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "score": {
        +      "description": "Your app's Search Visibility here — the same number as get_keywords `visibility.score` for this storefront.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "terms_scored": {
        +      "description": "The tracked non-brand terms every member's `visibility` is over.",
        +      "type": "integer"
        +    }
        +  },
        +  "type": [
        +    "object",
        +    "null"
        +  ]
        +}
    • Changedget_market3 fields changed
      • addedOutput schema / properties / entered / items / properties / visibility
        Added value: +{
        +  "description": "Search Visibility, 0–100, on the account's **tracked non-brand terms** in this storefront: the same score get_keywords gives your app, computed for this one — 100 = #1 on every term, the mean of the last 7 days. **Not `share` rescaled, and not in its order**: `share` is over this landscape's wider corpus (it adds the terms the app ranks top 10 for untracked, and brand terms) at each app's best place in the window. 0 is a measurement — nothing in the top 50 on those terms. Null when the account's terms here cannot be scored or the read failed — top-level `visibility.reason` / `caveats` say which.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / left / items / properties / visibility
        Added value: +{
        +  "description": "Search Visibility, 0–100, on the account's **tracked non-brand terms** in this storefront: the same score get_keywords gives your app, computed for this one — 100 = #1 on every term, the mean of the last 7 days. **Not `share` rescaled, and not in its order**: `share` is over this landscape's wider corpus (it adds the terms the app ranks top 10 for untracked, and brand terms) at each app's best place in the window. 0 is a measurement — nothing in the top 50 on those terms. Null when the account's terms here cannot be scored or the read failed — top-level `visibility.reason` / `caveats` say which.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / members / items / properties / visibility
        Added value: +{
        +  "description": "Search Visibility, 0–100, on the account's **tracked non-brand terms** in this storefront: the same score get_keywords gives your app, computed for this one — 100 = #1 on every term, the mean of the last 7 days. **Not `share` rescaled, and not in its order**: `share` is over this landscape's wider corpus (it adds the terms the app ranks top 10 for untracked, and brand terms) at each app's best place in the window. 0 is a measurement — nothing in the top 50 on those terms. Null when the account's terms here cannot be scored or the read failed — top-level `visibility.reason` / `caveats` say which.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
  2. 1 tool update
    • Changedget_keywords3 fields changed
      • addedOutput schema / properties / visibility / properties / overall
        Added value: +{
        +  "description": "The app's Search Visibility across those storefronts, weighted by where its downloads come from (`weights_source`). **Lead with this for \"is our ASO working\" without a storefront in the question;** with one, lead with `score`. Null at portfolio scope or when the read failed.",
        +  "properties": {
        +    "band": {
        +      "description": "Same cut-offs as `visibility.score.band`.",
        +      "enum": [
        +        "dominant",
        +        "strong",
        +        "visible",
        +        "weak",
        +        "invisible"
        +      ],
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "change": {
        +      "description": "The storefronts' like-for-like changes, weighted, over storefronts that have one. Null = no storefront had a baseline — not a change of zero.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "coverage": {
        +      "description": "0–1: the share of downloads the scored storefronts hold before rescaling. Low means the overall rests on a small part of where downloads come from. Null with equal weights.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "effective_rank": {
        +      "description": "The single place that earns this score on every term.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "formula_version": {
        +      "description": "Changes when the formula does; 1 now.",
        +      "type": "string"
        +    },
        +    "previous": {
        +      "description": "The same weighting over each storefront's `previous`, over storefronts that have one.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "reason": {
        +      "description": "Why there is no overall score.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "score": {
        +      "description": "0–100: the storefront scores weighted by each line's `weight`. Null when no storefront has a score — `reason` says why.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "storefronts_scored": {
        +      "description": "Storefronts that carry weight in `score`.",
        +      "type": "integer"
        +    },
        +    "terms_scored": {
        +      "description": "Non-brand terms across the storefronts that carry weight.",
        +      "type": "integer"
        +    },
        +    "weights_month": {
        +      "description": "YYYY-MM the weights belong to. They change only when the month does.",
        +      "type": "string"
        +    },
        +    "weights_source": {
        +      "description": "`downloads`: each storefront's share of the app's App Store Connect downloads over the 90 days before `weights_month` began, rescaled over the scored storefronts and fixed for the month. `equal`: the app is not connected to App Store Connect on this account, or had no downloads in a scored storefront.",
        +      "enum": [
        +        "downloads",
        +        "equal"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "terms_scored",
        +    "storefronts_scored",
        +    "weights_source",
        +    "weights_month",
        +    "formula_version"
        +  ],
        +  "type": [
        +    "object",
        +    "null"
        +  ]
        +}
      • changedOutput schema / properties / visibility / properties / storefronts / description
        Previous value: -"One line per storefront the app tracks terms in — score, change, band — so \"where is it working\" is one call. Each is its own reading over its own terms; never add them up. Empty at portfolio scope."New value: +"One line per storefront the app tracks terms in — score, change, band, weight — so \"where is it working\" is one call. Each is its own reading over its own terms; never add them up — `overall` weights them. Empty at portfolio scope."
      • addedOutput schema / properties / visibility / properties / storefronts / items / properties / weight
        Added value: +{
        +  "description": "0–1: what this storefront counts for in `visibility.overall.score` (see `weights_source` there). Null = it has no score, so it is not in the overall.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
  3. 2 tool updates
    • Changedget_keywords14 fields changed
      • changedInput schema / properties / include / description
        Previous value: -"Extra blocks. `history` adds a day-by-day series per term, capped at 25 terms — pair it with `keyword_ids` for specific terms. `visibility` adds the daily top-1/3/10/30/50/100 counts and their change, which is the number that survives a corpus changing size. `shared` adds the terms two of your own apps are both on, and needs `scope: \"portfolio\"`."New value: +"Extra blocks. `history` adds a day-by-day series per term, capped at 25 terms — pair it with `keyword_ids` for specific terms. `visibility` adds **Search Visibility** — the 0–100 answer to \"is our ASO working here\", its like-for-like change and the terms that moved it, plus a line per storefront — and each row's `points_change` and `brand`; also the daily top-N counts, which are over every term the app appeared for, not over this corpus. `shared` adds the terms two of your own apps are both on, and needs `scope: \"portfolio\"`."
      • addedOutput schema / properties / keywords / items / properties / brand
        Added value: +{
        +  "description": "Whether this term is somebody's name, for Search Visibility. Present only with `include: [\"visibility\"]` at app scope.",
        +  "properties": {
        +    "kind": {
        +      "description": "Null = scored. `own` = the app's own name; `competitor` = the name of the app ranking #1 for it. Either is listed and left out of Search Visibility.",
        +      "enum": [
        +        "own",
        +        "competitor"
        +      ],
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "override": {
        +      "description": "The owner's decision, set with tag_keywords `brand`; `none` = counted whatever detection says. Null = detection decides.",
        +      "enum": [
        +        "own",
        +        "competitor",
        +        "none"
        +      ],
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "reason": {
        +      "description": "Why detection flagged it, e.g. \"matches “Fuelio: MPG & Mileage Tracker”, which ranks #1\".",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    }
        +  },
        +  "type": [
        +    "object",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / keywords / items / properties / points_change
        Added value: +{
        +  "description": "What this term did to Search Visibility in its storefront over the window, in points — its exact share of `visibility.score.change`, so the rows' values add up to it. Present only with `include: [\"visibility\"]` at app scope; null for a brand term, a term not measured in both weeks, or when visibility was not asked for.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • changedOutput schema / properties / visibility / description
        Previous value: -"Terms in each band at the end of the window, cumulative, when `include: [\"visibility\"]` was asked for. **A state, not a sum** — the last day that has a row, because summing thirty days of \"terms in the top ten\" counts the same term thirty times. Null when it was not asked for, or when nothing has been rolled up for this app yet."New value: +"When `include: [\"visibility\"]` was asked for: Search Visibility (`score`, `storefronts`) and the daily band counts. **The band counts are not over this corpus**: they count every term the app appeared for that day in this storefront, tracked or not, so they move with whatever anybody crawled and can disagree with the rows (a corpus whose rows show 27 terms in the top 10 can read `top10: 20`). Read them as reach across the store, never as the corpus's state. Each is the last day with a row — a state, not a sum — and null when nothing has been rolled up. The whole block is null when visibility was not asked for."
      • changedOutput schema / properties / visibility / properties / ranked / type
        Previous value: -"integer"New value: +[
        +  "integer",
        +  "null"
        +]
      • addedOutput schema / properties / visibility / properties / score
        Added value: +{
        +  "description": "**Search Visibility** in the storefront asked about: 0–100, 100 = #1 on every non-brand tracked term, with its like-for-like `change`, the terms that moved it (`biggest_losses`, `biggest_gains`) and the rivals in `compare_with` scored on the same terms. **Lead with this for \"is our ASO working\"** — counting risers and fallers points the wrong way when a term at #1 drops to #7. Over every tracked term in the storefront, whatever filter the rows had. Null at portfolio scope (niches do not add up) or when the read failed — the caveats say which.",
        +  "properties": {
        +    "band": {
        +      "description": "Banded on effective rank: dominant 60+ (≈#1–2 everywhere, owns the first screen), strong 35–59 (≈#2–4), visible 18–34 (≈#4–10), weak 9–17 (≈#10–25), invisible 0–8 (beyond #25). Null with `score`.",
        +      "enum": [
        +        "dominant",
        +        "strong",
        +        "visible",
        +        "weak",
        +        "invisible"
        +      ],
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "biggest_gains": {
        +      "description": "The terms that added the most points, best first, at most 5.",
        +      "items": {
        +        "properties": {
        +          "keyword_id": {
        +            "description": "Apptail keyword id.",
        +            "type": "integer"
        +          },
        +          "place_now": {
        +            "description": "Its place on the last day of the headline week. Null = not in the top 50 (it was measured).",
        +            "type": [
        +              "integer",
        +              "null"
        +            ]
        +          },
        +          "place_then": {
        +            "description": "Its place on the last day of the earlier week. Null = not in the top 50 (it was measured).",
        +            "type": [
        +              "integer",
        +              "null"
        +            ]
        +          },
        +          "points_change": {
        +            "description": "What it did to the score, in points. These add up to `change` across all terms.",
        +            "type": "number"
        +          },
        +          "term": {
        +            "description": "The term.",
        +            "type": "string"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "biggest_losses": {
        +      "description": "The terms that cost the most points, worst first, at most 5 — the answer to \"why did it fall\". Counted over the whole storefront corpus, whatever filter the rows had.",
        +      "items": {
        +        "properties": {
        +          "keyword_id": {
        +            "description": "Apptail keyword id.",
        +            "type": "integer"
        +          },
        +          "place_now": {
        +            "description": "Its place on the last day of the headline week. Null = not in the top 50 (it was measured).",
        +            "type": [
        +              "integer",
        +              "null"
        +            ]
        +          },
        +          "place_then": {
        +            "description": "Its place on the last day of the earlier week. Null = not in the top 50 (it was measured).",
        +            "type": [
        +              "integer",
        +              "null"
        +            ]
        +          },
        +          "points_change": {
        +            "description": "What it did to the score, in points. These add up to `change` across all terms.",
        +            "type": "number"
        +          },
        +          "term": {
        +            "description": "The term.",
        +            "type": "string"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "change": {
        +      "description": "**Like-for-like** change in points: over the terms measured in both weeks only, so adding or removing terms cannot move it. Each row's `points_change` is its exact share. Null when the earlier week has no baseline — not a change of zero.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "compared": {
        +      "description": "The apps in `compare_with`, scored on this app's corpus — the same rule as the rows' `competitors`. Empty when nothing was compared.",
        +      "items": {
        +        "properties": {
        +          "app_id": {
        +            "description": "Apptail id of the rival from `compare_with`.",
        +            "type": "integer"
        +          },
        +          "band": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "change": {
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "name": {
        +            "description": "Its name.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "previous": {
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "score": {
        +            "description": "Its score on YOUR terms, same weights, same weeks.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "corpus_change": {
        +      "description": "The list moving rather than the app. History is recomputed over today's list, so a term added last week still has its earlier places if anyone crawled it; only terms with no earlier crawl land here.",
        +      "properties": {
        +        "added": {
        +          "description": "Terms scored now that were not measured in the earlier week.",
        +          "type": "integer"
        +        },
        +        "effect": {
        +          "description": "What they did to `score − previous`, in points. Never part of `change`.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "removed": {
        +          "description": "Terms measured in the earlier week that are not now.",
        +          "type": "integer"
        +        }
        +      },
        +      "type": "object"
        +    },
        +    "country": {
        +      "description": "The storefront this score is for.",
        +      "type": "string"
        +    },
        +    "effective_rank": {
        +      "description": "The single place that would earn this score on every term: S = 21 is \"as visible as ranking about #8 everywhere\". The sentence to put beside the number. Null for no score or a score of 0.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "formula_version": {
        +      "description": "Changes when a constant behind the score changes; scores from different versions do not compare.",
        +      "type": "string"
        +    },
        +    "gained": {
        +      "description": "Sum of the positive `points_change` — what the terms that improved added.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "last_measured_on": {
        +      "description": "The most recent full-depth crawl of any of these terms, `YYYY-MM-DD`.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "lost": {
        +      "description": "Sum of the negative `points_change`. `gained + lost = change`.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "previous": {
        +      "description": "The same score over the 7 days before the window began. `score − previous = change + corpus_change.effect`.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "reason": {
        +      "description": "Why `score` is null, with the fix — \"Track 5 or more non-brand terms in DE to get a score\". Null when there is a score.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "score": {
        +      "description": "0–100: the app's weighted search places over the most its non-brand tracked terms allow. **100 = #1 on every one.** The mean of the last 7 days of the window. Null when fewer than 5 non-brand terms were measured — `reason` says why — and never 0 for missing: 0 is a measurement, nothing in the top 50.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "terms_brand_excluded": {
        +      "description": "Brand terms listed and left out: the app's own name (it ranks #1 whatever its ASO does) and rivals' names where that rival ranks #1. Detected from title words; an owner can override per term with tag_keywords `brand`. Rows carry `brand`.",
        +      "type": "integer"
        +    },
        +    "terms_scored": {
        +      "description": "Non-brand terms measured in the last 7 days — what `score` is over. Quote it beside the score.",
        +      "type": "integer"
        +    },
        +    "terms_unmeasured": {
        +      "description": "Non-brand terms with no full-depth crawl in the last 7 days. Out of this week's score — not counted as zero.",
        +      "type": "integer"
        +    },
        +    "weeks": {
        +      "description": "The two 7-day spans `score` and `previous` are over, `YYYY-MM-DD`.",
        +      "properties": {
        +        "now_from": {
        +          "type": "string"
        +        },
        +        "now_to": {
        +          "type": "string"
        +        },
        +        "then_from": {
        +          "type": "string"
        +        },
        +        "then_to": {
        +          "type": "string"
        +        }
        +      },
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "country",
        +    "biggest_losses",
        +    "biggest_gains",
        +    "terms_scored",
        +    "terms_brand_excluded",
        +    "terms_unmeasured",
        +    "corpus_change",
        +    "compared",
        +    "weeks",
        +    "formula_version"
        +  ],
        +  "type": [
        +    "object",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / visibility / properties / storefronts
        Added value: +{
        +  "description": "One line per storefront the app tracks terms in — score, change, band — so \"where is it working\" is one call. Each is its own reading over its own terms; never add them up. Empty at portfolio scope.",
        +  "items": {
        +    "properties": {
        +      "band": {
        +        "enum": [
        +          "dominant",
        +          "strong",
        +          "visible",
        +          "weak",
        +          "invisible"
        +        ],
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "change": {
        +        "description": "Like-for-like change in points. Null = no baseline.",
        +        "type": [
        +          "number",
        +          "null"
        +        ]
        +      },
        +      "country": {
        +        "description": "Storefront code.",
        +        "type": "string"
        +      },
        +      "reason": {
        +        "description": "Why there is no score here, with the fix.",
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "score": {
        +        "description": "0–100, as `visibility.score.score`. Null with a `reason`.",
        +        "type": [
        +          "number",
        +          "null"
        +        ]
        +      },
        +      "terms_scored": {
        +        "description": "Non-brand terms the score is over.",
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "country",
        +      "terms_scored"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / visibility / properties / top1 / type
        Previous value: -"integer"New value: +[
        +  "integer",
        +  "null"
        +]
      • changedOutput schema / properties / visibility / properties / top10 / type
        Previous value: -"integer"New value: +[
        +  "integer",
        +  "null"
        +]
      • changedOutput schema / properties / visibility / properties / top100 / type
        Previous value: -"integer"New value: +[
        +  "integer",
        +  "null"
        +]
      • changedOutput schema / properties / visibility / properties / top3 / type
        Previous value: -"integer"New value: +[
        +  "integer",
        +  "null"
        +]
      • changedOutput schema / properties / visibility / properties / top30 / type
        Previous value: -"integer"New value: +[
        +  "integer",
        +  "null"
        +]
      • changedOutput schema / properties / visibility / properties / top50 / type
        Previous value: -"integer"New value: +[
        +  "integer",
        +  "null"
        +]
      • addedOutput schema / properties / visibility / required
        Added value: +[
        +  "storefronts"
        +]
    • Changedtag_keywords7 fields changed
      • addedInput schema / properties / brand
        Added value: +{
        +  "description": "Whether these terms are somebody's name, for Search Visibility (get_keywords `include: [\"visibility\"]`). `own` = your app's name, `competitor` = a rival's: both are listed and left out of the score, because you rank #1 for your own name whatever your ASO does. `none` = count them anyway. `auto` = let title-word detection decide again. Detection is shown on each row's `brand.reason`; override it when it is wrong (a transliterated rival name, a generic word it took for a name). Omit to leave the brand decision alone. Sent without `tag`, only the brand decision changes.",
        +  "enum": [
        +    "own",
        +    "competitor",
        +    "none",
        +    "auto"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / properties / action / description
        Previous value: -"`assigned` when a tag was set, `cleared` when the tag was taken off."New value: +"`assigned` when a tag was set, `cleared` when the tag was taken off, `untouched` when only `brand` was sent and the tags were left as they were."
      • changedOutput schema / properties / action / enum
        Previous value: -[
        -  "assigned",
        -  "cleared"
        -]New value: +[
        +  "assigned",
        +  "cleared",
        +  "untouched"
        +]
      • addedOutput schema / properties / brand
        Added value: +{
        +  "description": "The brand decision applied (`own`, `competitor`, `none`, `auto`), or null when none was sent. Search Visibility recomputes on the next get_keywords read.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / brand_changed_count
        Added value: +{
        +  "description": "How many keywords took the brand decision. 0 when `brand` was not sent.",
        +  "type": "integer"
        +}
      • changedOutput schema / properties / changed_count / description
        Previous value: -"How many keywords were actually changed."New value: +"How many keywords had their tag changed. 0 on a brand-only call."
      • changedOutput schema / required
        Previous value: -[
        -  "app_id",
        -  "action",
        -  "tag_created",
        -  "changed_count",
        -  "requested_count",
        -  "unknown_ids",
        -  "tags"
        -]New value: +[
        +  "app_id",
        +  "action",
        +  "tag_created",
        +  "brand_changed_count",
        +  "changed_count",
        +  "requested_count",
        +  "unknown_ids",
        +  "tags"
        +]
  4. 25 tool updates
    • First observedadd_app
    • First observedadd_competitors
    • First observedadd_keywords
    • First observeddiscover_keywords
    • First observededit_market_corpus
    • First observedexplain_period
    • First observedget_account
    • First observedget_app
    • First observedget_competitors
    • First observedget_keyword_serp
    • First observedget_keywords
    • First observedget_landscape
    • First observedget_market
    • First observedget_performance
    • First observedget_reviews
    • First observedget_signals
    • First observedget_top_charts
    • First observedlist_markets
    • First observedremove_competitors
    • First observedremove_keywords
    • First observedremove_market
    • First observedreply_to_reviews
    • First observedsave_market
    • First observedsearch_apps
    • First observedtag_keywords

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    B
    maintenance
    Enables App Store and Google Play keyword rank tracking, competitor comparisons, and AI visibility checks through natural language, without requiring store credentials.
    85
    32 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    App Store and Google Play ASO: keyword rankings, competitors, reviews, audits and an action queue.
    90
    AGPL 3.0
  • A
    license
    A
    quality
    A
    maintenance
    Connects App Store and Google Play keyword data to AI assistants, enabling ASO tasks like rank tracking, keyword analysis, competitor visibility, and top charts through natural language.
    30
    407 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants to query live App Store and Google Play data — keyword demand, rank history, top charts, competitor sets, reviews and ratings — across 100+ countries, directly inside a chat instead of a dashboard. Exposes 35 read-only tools covering app search, ASO keyword profiling, ranking trajectories, and worldwide market comparisons through a remote OAuth-authenticated endpoint.
    2
    MIT
Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources