Concepts

The handful of rules every endpoint follows. Learn them once and the whole API reads the same way.

App identifiers: {app}

Every app-scoped endpoint takes one path segment that accepts anything that identifies an app. URL-encode it.

  • App Store id: 284882215 or id284882215
  • Google Play package: com.spotify.music
  • Apple bundle id: com.spotify.client
  • A store URL (apps.apple.com, itunes.apple.com, play.google.com)
  • A prefixed id: apple:284882215, play:com.spotify.music
  • A name: spotify resolves to the best search match and says so in meta.warnings

Add store=apple or store=play when an identifier could belong to either store. data.linked is the same app in the other store when we know it (same bundle id, or same developer and name).

Freshness

There is no separate fetch endpoint. App-scoped reads refresh data from the store when ours is older than max_age, and tell you how old it is in meta.freshness.

  • max_age: seconds. Defaults to 7 days for app data and 1 day for reviews; 0 always refreshes. A refresh costs 1 credit per country fetched (at most 10).
  • wait: true (default) blocks up to about 25 seconds for the refresh. On timeout, or with wait=false, you get the cached data with refreshing: true and a job_id. Poll GET /v1/jobs/{id} (free) until it is done, then repeat the call.
  • country: us by default. Paid plans can pass a comma list (us,de,jp); Studio can pass all.

An app we have never seen is fetched on the spot. If that takes longer than the wait, the answer is HTTP 202 with data: null and a job_id.

Response envelope

Every success is { data, meta }. data is an object or a list; meta carries what the call cost and how fresh the answer is.

{
  "data": { "store": "apple", "app_id": "324684580", "title": "Spotify: Music and Podcasts", … },
  "meta": {
    "credits_used": 1,
    "credits_remaining": 9981,
    "request_id": "req_123",
    "store": "apple",
    "freshness": {
      "fetched_at": "2026-10-22T15:03:07Z",
      "age_seconds": 3600,
      "stale": false,
      "refreshing": false,
      "job_id": null
    },
    "next_cursor": null,
    "warnings": []
  }
}
  • credits_used: what this call cost. credits_remaining: monthly plus pack credits left.
  • freshness: when the oldest requested country was fetched from the store, whether it is stale, and a job to poll if a refresh is still running.
  • next_cursor: the next page, or null.
  • warnings: things worth telling the user (for example that an app was resolved by name).
  • request_id: quote it when you contact support.

Credits

Plans come with monthly credits; every call reports its cost. Failed requests are free. Tracked keywords are a plan allowance, not credits. The cost of each endpoint, from the OpenAPI document:

EndpointCredits
GET/apps/{app}1+ credits1 + 1 per country refreshed from the store (max 10)
GET/apps/{app}/reviews1+ credits1 per page + 1 if the US reviews are refreshed
GET/apps/{app}/history1+ credits1 + 1 per country refreshed (max 10)
GET/apps/{app}/changes1+ credits1 + 1 if the US listing is refreshed
GET/apps/{app}/keywords1 credit
GET/apps/{app}/competitors1 credit
GET/apps/{app}/insights25 credits
POST/apps/{app}/audit100+ credits100 (the first audit of an organization is free)
GET/search1 credit
GET/charts1 credit
GET/keywords1+ credits1 per keyword; 5 per seed with include=related or suggestions
GET/tracked-keywordsFree
POST/tracked-keywordsFree
GET/tracked-keywords/{id}Free
PATCH/tracked-keywords/{id}Free
DELETE/tracked-keywords/{id}Free
GET/tracked-keywords/{id}/ranks1 credit
POST/tracked-keywords/{id}/run1+ credits1 when queued, 0 when the last hour's ranking is returned
GET/developers1+ credits1 per page
GET/developers/{dev}1 credit
GET/auditsFree
GET/audits/{id}Free
PATCH/audits/{id}Free
GET/watchesFree
POST/watchesFree
GET/watches/{id}Free
PATCH/watches/{id}Free
DELETE/watches/{id}Free
GET/eventsFree
GET/webhooksFree
POST/webhooksFree
GET/webhooks/{id}Free
PATCH/webhooks/{id}Free
DELETE/webhooks/{id}Free
POST/webhooks/{id}/testFree
GET/webhooks/{id}/deliveriesFree
GET/connectionsFree
POST/connectionsFree
GET/connections/{id}Free
PATCH/connections/{id}Free
DELETE/connections/{id}Free
POST/connections/{id}/verifyFree
GET/connections/{id}/reviews1 credit
GET/connections/{id}/listings1 credit
GET/connections/{id}/ads1 credit
GET/actionsFree
POST/actions0+ credits0 (dry run, pending approval) or 1 when auto-applied
GET/actions/{id}Free
POST/actions/{id}/approve1+ credits1 when applied, else 0
POST/actions/{id}/rejectFree
GET/opportunities50 credits
GET/jobs/{id}Free
GET/openapi.jsonFree
GET/meFree
PlanPriceCredits / monthTracked keywordsWatched appsRequests / secondCredit packs
FreeFree2502531No
Indie$27/mo10,0002,5002510Yes
Studio$147/mo75,00010,00025025Yes

Credit packs (2,000 for $17, 7,000 for $47, 17,000 for $97) are available on Indie and Studio, never expire and are used after the monthly credits. See pricing.

Pagination and fields

  • Lists page with limit and cursor: pass meta.next_cursor back unchanged until it is null. Each page of reviews is charged separately.
  • include= adds optional sections (for an app: markets, iap, versions, privacy, estimates, charts).
  • fields= keeps only the listed top-level fields (identifiers are always kept), which keeps agent context small.

Errors

Errors use the HTTP status and one body shape, whoever answered. Read hint: it says what to do next.

{
  "error": {
    "code": "insufficient_credits",
    "message": "Out of credits: monthly plan credits and credit packs are used up.",
    "hint": "Upgrade your plan, or on Indie / Studio buy a credit pack …",
    "docs": "https://appgoat.ai/docs/errors#insufficient_credits"
  }
}

15 codes, from invalid_param to upstream_unavailable, are listed with causes and fixes on Errors.

Rate limits

Requests per second are per organization: Free 1, Indie 10, Studio 25. Over the limit you get 429 rate_limited with a Retry-After header.