← All playbooks
Playbook

Reply to every review

Drafts a reply to every unanswered App Store and Google Play review of the user's own app, in the app's voice, through their connected App Store Connect / Play Console account – each reply is a dry run with a before/after diff and AppGoat guardrail scores (policy, tone, claims), and only the replies the user approves are posted.

~1–3 + 1 per posted reply credits a run/reply-to-every-review [app] [since]

Finds the reviews of the user's app that have no developer reply, writes a short, specific reply to each, and posts the ones the user approves. Nothing is posted without a dry run the user has seen: every reply goes through propose_action (dry run, free) and then apply_action.

Install

The appgoat/skills repository is private until its public release, so the plugin and git commands below are coming soon. Today, run the playbooks as prompts of the AppGoat MCP server (Claude, Cursor, ChatGPT) or download the SKILL.md from this page.

Install the AppGoat plugin (all playbooks plus the AppGoat MCP server), then run /mcp to sign in.

/plugin marketplace add appgoat/skills
/plugin install appgoat-playbooks@appgoat

Run it by asking for it in plain words, or by name:

/reply-to-every-review [app] [since]

When to use

  • The user owns the app and has connected App Store Connect and/or Play Console in AppGoat (https://dash.appgoat.ai/connections).
  • Not for competitors' apps (use app-audit-lite) or for filing bugs (use reviews-to-github-issues).

Inputs

  • app (optional): a linked app (App Store id or Play package). Default: every app linked to an asc or play_console connection; ask which one when there are more than three.
  • since (optional): ISO date; default all unanswered reviews (Play only returns the last 7 days).

Ask the user once, before drafting, for anything you cannot infer: the app's voice (default: friendly, plain, first person plural "we"), the support address to point to (default: the support URL in the listing), and whether there is a known fix or release to mention.

Limits per run unless the user says otherwise: at most 30 replies, lowest stars and newest first.

Steps

Costs in AppGoat credits. Without the MCP, use the REST equivalents in the REST calls below.

  1. list_connections {} - 0. Keep asc and play_console connections with status: active and the app linked. mode: read_only can dry-run but not post: tell the user and continue with drafts only. No connection: send the user to https://dash.appgoat.ai/connections and stop.
  2. For each connection and app: read_connection {id, what: "reviews", app, unanswered: true, limit: 100} - 1 per page, follow next_cursor up to 3 pages. Each review has a review_id, rating, title, body, language, version and date. Drop reviews older than since.
  3. Optional prioritising: get_reviews {app, rating: "1-3", intent: "bug,crash", limit: 100} - 1 returns AppGoat's AI tags (intent, severity, topic) for public reviews. Match by text and date; answer bug/crash reviews with severity ≥ 2 first and group same-topic reviews so their replies are consistent (not identical).
  4. Draft one reply per review (no calls), following the decision rules. Reply in the review's language.
  5. For each draft: propose_action {type: "review.reply", connection: <id>, params: {app, review_id, text}, playbook: "reply-to-every-review"} - 0. It returns the action id, the before → after diff (an existing reply shows as before) and the guardrail result. A BLOCKS check (e.g. over 350 characters on Play) means: shorten or fix and propose again.
  6. Show the user one table of all drafts (see Output format), flagged ones first, and ask which to post: "all", "all except 3, 7", or edits ("4: mention the export fix"). Re-propose edited ones.
  7. For each approved row: apply_action {id} - 1 when posted. Outcomes:
    • applied: posted (App Store replies appear after Apple's review, usually within 24 h).
    • pending_approval: the org requires a person to approve in the dashboard; collect the approve_urls and give them to the user (one link per action, or https://dash.appgoat.ai/approvals).
    • "needs approval by a person" (403): same, hand over the link, do not retry.
    • "account changed" (409): someone replied meanwhile; show the new diff and skip unless the user wants to replace it (force: true).
  8. reject_action {id, reason} - 0 for drafts the user declined, so they leave the approvals list.
  9. Report what was posted, what waits for approval and what was skipped.

Budget: 1–3 credits to read + 1 per posted reply (dry runs and pending approvals are free).

Decision rules

  • Answer the review, not a template. Reference one or two concrete points the user made (the feature, the bug, the version). Never copy the same reply across reviews.
  • Length: 2–4 sentences. Play replies ≤ 350 characters (hard limit); App Store ≤ 600 as a house rule (limit 5970). Count before proposing.
  • Negative (1–2★): thank, acknowledge the specific problem without arguing, give one concrete next step (update to the version with the fix if the user confirmed one, a setting, or the support address). Ask for details only through the support channel.
  • Neutral (3★): thank, answer the question or suggestion, say if it is on the roadmap only if the user confirmed it.
  • Positive (4–5★): thank them for something specific; one sentence is fine. No upsell.
  • Never: promise dates, refunds, features or fixes the user did not confirm; offer incentives for changing the rating (against both stores' policies); ask for a better rating; include personal data (names, emails, order ids) of the reviewer or staff; blame the user or Apple/Google; link anywhere but the app's own support or privacy pages.
  • Skip (do not reply, list them): spam, abusive reviews, reviews that are only a star with no text on Play, and legal threats (flag those to the user).
  • Guardrail: flagged or any check under 0.7 → rewrite once before showing it; if still flagged, show it with the flag and let the user decide. Never apply a draft whose guardrail shows BLOCKS.
  • Existing replies: this playbook only answers unanswered reviews; replacing a reply needs the user to ask for it.

Output format

Draft table (before step 7):

# Review replies: <app> (<store>) - <n> unanswered, <k> drafted

| # | ★ | Review (excerpt) | Reply draft | Chars | Guardrail | Action |
|---|---|---|---|---|---|---|
| 1 | 1 | "Login loops forever since 2.4…" | "Sorry about the login loop since 2.4…" | 212 | flagged: claims 0.62 (promises a fix) | act_… |
| 2 | 5 | "Love the widgets" | "Thank you! The widgets are…" | 96 | passed 0.95 | act_… |

Skipped: 2 spam, 1 legal threat (#14, please read it yourself).
Reply "post all", "post all except 1", or edit a row.

Run report (after step 7):

Posted 18 replies (App Store replies go live after Apple's review).
Waiting for approval in the dashboard (4): https://dash.appgoat.ai/approvals
Skipped 3, rejected 2 drafts. Credits used: <sum>.

Automate it

Daily drafts that a person approves in the AppGoat dashboard. The run calls apply_action once per draft, which submits it for approval (or posts it when the org has a Studio auto-apply rule for review.reply that the guardrail score clears). Without such a rule nothing is posted until someone approves it at https://dash.appgoat.ai/approvals.

GitHub Action (.github/workflows/appgoat-review-replies.yml, secrets ANTHROPIC_API_KEY, APPGOAT_API_KEY):

name: AppGoat review replies
on:
  schedule:
    - cron: "0 7 * * *"
  workflow_dispatch:
permissions:
  contents: read
jobs:
  replies:
    runs-on: ubuntu-latest
    steps:
      - name: Install AppGoat playbooks
        run: |
          git clone --depth 1 https://github.com/appgoat/skills /tmp/appgoat-skills
          mkdir -p .claude/skills
          cp -r /tmp/appgoat-skills/skills/* .claude/skills/
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            Use the reply-to-every-review skill for all linked apps. This is an unattended run: use a friendly,
            plain voice and the support URL from the listing, promise nothing. Instead of asking the user in step 6,
            call apply_action once for every draft whose guardrail passed (this submits it for approval in the
            dashboard), and reject_action the rest with the reason. Never call apply_action with force.
          claude_args: |
            --mcp-config '{"mcpServers":{"appgoat":{"command":"npx","args":["-y","@appgoat/mcp"],"env":{"APPGOAT_API_KEY":"${{ secrets.APPGOAT_API_KEY }}"}}}}'
            --allowedTools mcp__appgoat__list_connections,mcp__appgoat__read_connection,mcp__appgoat__get_reviews,mcp__appgoat__propose_action,mcp__appgoat__apply_action,mcp__appgoat__reject_action,mcp__appgoat__get_action

Claude Code routine:

/schedule weekdays 09:00 - Run the reply-to-every-review playbook for my linked apps. Draft replies, submit the ones whose guardrail passed for approval (apply_action once each), and send me the approvals link with a one-line summary per app.

Without MCP: the REST calls

Without the AppGoat MCP server, call the API directly. Base URL https://api.appgoat.ai/v1, header x-api-key: $APPGOAT_API_KEY (create a key at https://dash.appgoat.ai). Responses are { "data": …, "meta": { "credits_used", "credits_remaining", "next_cursor", … } }; errors are { "error": { "code", "message", "hint", "approve_url"? } } - read hint.

MCP toolREST call
list_connectionsGET /connections (free)
read_connection what=reviewsGET /connections/{id}/reviews?app={app}&unanswered=true&limit=100 (next page: &cursor=<meta.next_cursor>)
get_reviewsGET /apps/{app}/reviews?rating=1-3&intent=bug,crash&limit=100
propose_actionPOST /actions {"type":"review.reply","connection":"con_…","dry_run":true,"source":"playbook:reply-to-every-review","params":{"app":"…","review_id":"…","text":"…"}} (free)
apply_actionPOST /actions/{id}/approve {} or {"force":true} (1 credit when applied)
reject_actionPOST /actions/{id}/reject {"reason":"…"} (free)
get_actionGET /actions/{id} (free)
CON=con_x8Hq2mXc0aLpQwRtYb APP=1234567890
curl -s -G -H "x-api-key: $APPGOAT_API_KEY" "https://api.appgoat.ai/v1/connections/$CON/reviews" \
  -d app=$APP -d unanswered=true -d limit=100 \
  | jq -r '.data[] | "\(.rating)★ \(.created_at[0:10]) \(.id) \(.body[0:160])"'

curl -s -H "x-api-key: $APPGOAT_API_KEY" -H "content-type: application/json" \
  https://api.appgoat.ai/v1/actions -d @- <<JSON | jq '.data | {id, status, diff, guardrail}'
{"type":"review.reply","connection":"$CON","dry_run":true,"source":"playbook:reply-to-every-review",
 "params":{"app":"$APP","review_id":"<id>","text":"Thanks for the detailed report! …"}}
JSON

Approve outcomes: 200 with status applied or pending_approval (approve_url), 403 forbidden (a person approves at error.approve_url), 409 conflict with error.diff when someone replied since the dry run.

More playbooks

/app-audit-lite~25–40 credits a run

App audit (lite)

Runs a quick App Store / Google Play audit of one app with live AppGoat data – listing quality, ratings trend, recent complaints, keyword rankings, competitors and their recent changes – and ends with a prioritized fix list.

/find-app-ideas~150–250 credits a run

Find app ideas

Finds underserved App Store / Google Play niches with live AppGoat data – search demand (keyword popularity) against weak supply (low ratings, abandoned incumbents, few strong apps) – and returns a ranked shortlist of app ideas with evidence and user pain points.

/aso-keywords-metadata-rewrite~40–80 credits a run

ASO keywords → metadata rewrite

Researches App Store / Google Play keywords for one app with live AppGoat data (current ranks, competitor keyword gaps, popularity, difficulty, relevance) and rewrites the title, subtitle, keyword field and description within the store character limits.

/competitor-digest~15–55 credits a run

Competitor digest

Writes a weekly competitor digest for one App Store / Google Play app from an AppGoat competitor watch – what each competitor changed (title, subtitle, description, screenshots, icon), price and in-app purchase changes, new versions with release-note summaries, rating and chart moves, keyword gains and losses against the user's app, keyword gaps and review themes – and ends with 3 recommended actions linked to other playbooks. Sets up or reuses the competitor_set watch.

/reviews-to-github-issues~2–10 credits a run

Reviews → GitHub issues

Turns recent negative App Store / Google Play reviews into deduplicated GitHub issues – one issue per problem topic, with linked and quoted reviews, new reviews added as comments to the existing issue instead of duplicates. Uses AppGoat review data and the gh CLI (or the GitHub MCP server).

/apple-ads-optimizer~5–8 per campaign + 1 per applied change credits a run

Apple Ads optimizer

Optimizes the user's Apple Ads (Search Ads) campaigns through their connected Apple Ads account – reads the last 14 days of keyword and search term reports, raises bids on keywords that convert under the target CPA (capped +30% per run), lowers or pauses keywords that spend without installs, adds converting search terms as exact keywords and wasteful ones as negatives – every change a dry run with a diff the user approves.

/localization-push~35–60 per locale + 1 per applied change credits a run

Localization push

Localizes the user's own App Store / Google Play listing into new languages through their connected App Store Connect or Play Console account – picks target locales from markets with demand, researches the keywords people actually search in each country with AppGoat, writes a localized name, subtitle, keyword field and promotional text (Play title, short and full description) within the store limits, and pushes each locale as a dry run with a before/after diff the user approves.