Skip to main content
Glama

AppTail – ASO & Keyword Research

Get Market

get_market

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.

Input Schema

TableJSON 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

TableJSON Schema
NameRequiredDescriptionDefault
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.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema 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. First observed

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.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources