RORK LABJP
RORKMAX — Rork Max generates pure Swift instead of React Native, enabling true native apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessageAPPLE — Rork's 2026 direction has a clear theme of native empowerment across the Apple ecosystemEXPO — Standard builds run on React Native and Expo, so you're left with a real project structure and code you can keep working onFUNDING — Rork recently raised $15M and now sees over 743,000 monthly visits with 85% growthPRICING — Rork is free to start, with paid plans from $25/month and Rork Max at $200/monthCROSS — Rork builds iOS, Android, and web from a single prompt, finished off with a bit of follow-up tweakingRORKMAX — Rork Max generates pure Swift instead of React Native, enabling true native apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessageAPPLE — Rork's 2026 direction has a clear theme of native empowerment across the Apple ecosystemEXPO — Standard builds run on React Native and Expo, so you're left with a real project structure and code you can keep working onFUNDING — Rork recently raised $15M and now sees over 743,000 monthly visits with 85% growthPRICING — Rork is free to start, with paid plans from $25/month and Rork Max at $200/monthCROSS — Rork builds iOS, Android, and web from a single prompt, finished off with a bit of follow-up tweaking
Articles/Business
Business/2026-04-20Advanced

Semi-Automating App Store Review Responses with AI — App Store Connect API × Gemini Field Notes

Field notes on building an AI-drafted, human-approved review response pipeline that I run across multiple iOS and Android apps. Includes 50M-download-scale operational lessons, Crashlytics integration patterns, and confidence-threshold tuning that aren't in any official documentation.

App Store Connect API3Google Play APIGemini API5review managementCloudflare Workers23automation7Rork Max223

Premium Article

There's a task that catches up with every app developer eventually: responding to user reviews on the App Store and Google Play.

From "love this app, using it every day!" to "crashes constantly, can't use it," keeping up with thoughtful replies is a time cost that's easy to underestimate. When I tracked it carefully across multiple apps, I was spending an average of 32 minutes per day on review monitoring and responses — about 16 hours a month. Not nothing.

I've been running iOS and Android apps as an indie developer since 2014. Cumulative downloads across the portfolio passed 50 million a while back, and I'm still actively maintaining several wallpaper and ambient-content apps today. Out of every operational chore in that workflow, review responses were the last holdout — the one task that resisted automation longest. Pasting a generic template tanks the rating; ignoring reviews tanks first-impression conversion. So I built a pipeline that drafts replies with AI and lets me approve them with a single tap. Below is the full architecture, plus the Rork-built mobile dashboard and Cloudflare Workers backend code I run in production.

"Won't the responses feel robotic?" is the natural concern. In practice, Gemini generates responses that feel like a real developer wrote them, understanding your app's context and adjusting tone by territory. The first time you see it in action, the quality is genuinely surprising.

Why Review Responses Actually Move Your Ratings

Both Apple and Google have data suggesting that developer responsiveness improves search ranking. Sensor Tower's research indicates that reviews receiving developer responses see an average rating improvement of 0.3–0.5 stars.

The more impactful effect is this: when you respond thoughtfully to a one-star review, users frequently update their rating. That "someone actually listened" experience turns into word-of-mouth. It's one of the few levers solo developers have that scales without a marketing budget.

The reasons responses pile up unaddressed are equally real though. Running multiple apps means just checking all the platforms takes time. Writing variations of the same bug acknowledgment for the hundredth time is tedious. Non-English reviews (Korean, German, Arabic) require translation effort. And emotionally charged one-star reviews take mental energy to engage with constructively.

The solution here is a hybrid model: AI drafts the response, you approve it. Full automation is tempting but wrong — a final human review means nothing problematic goes out. High-confidence responses (clear, unambiguous cases) get sent automatically; uncertain ones come to you.

App Store Connect API Setup

Generating Your API Key

Head to https://appstoreconnect.apple.com/access/integrations/api and create a new Team Key. A critical detail: the .p8 private key file can only be downloaded once at creation time. Store it securely immediately.

You'll need three values:

  • Issuer ID: The UUID shown at the top of the API keys page
  • Key ID: The 10-character alphanumeric string shown after key creation
  • Private Key: The contents of the downloaded .p8 file

Implementing JWT Authentication

App Store Connect API uses JWT authentication with the ES256 algorithm. Here's the Cloudflare Workers implementation:

// src/appstore/auth.ts
// JWT token generation for App Store Connect API
 
interface AppStoreConnectConfig {
  issuerId: string;
  keyId: string;
  privateKey: string; // .p8 file content, header/footer lines stripped
}
 
async function generateAppStoreToken(
  config: AppStoreConnectConfig
): Promise<string> {
  const header = {
    alg: "ES256",
    kid: config.keyId,
    typ: "JWT",
  };
 
  const now = Math.floor(Date.now() / 1000);
  const payload = {
    iss: config.issuerId,
    iat: now,
    exp: now + 19 * 60, // 19 min — stays safely under the 20-minute maximum
    aud: "appstoreconnect-v1",
  };
 
  const encodeBase64Url = (obj: object): string =>
    btoa(JSON.stringify(obj))
      .replace(/\+/g, "-")
      .replace(/\//g, "_")
      .replace(/=/g, "");
 
  const headerEncoded = encodeBase64Url(header);
  const payloadEncoded = encodeBase64Url(payload);
  const signingInput = `${headerEncoded}.${payloadEncoded}`;
 
  // ES256 signing via Cloudflare Workers WebCrypto API
  const pemKey = `-----BEGIN PRIVATE KEY-----\n${config.privateKey}\n-----END PRIVATE KEY-----`;
  const keyBuffer = pemToBuffer(pemKey);
 
  const cryptoKey = await crypto.subtle.importKey(
    "pkcs8",
    keyBuffer,
    { name: "ECDSA", namedCurve: "P-256" },
    false,
    ["sign"]
  );
 
  const encoder = new TextEncoder();
  const signature = await crypto.subtle.sign(
    { name: "ECDSA", hash: "SHA-256" },
    cryptoKey,
    encoder.encode(signingInput)
  );
 
  const signatureEncoded = btoa(
    String.fromCharCode(...new Uint8Array(signature))
  )
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
 
  return `${signingInput}.${signatureEncoded}`;
}
 
function pemToBuffer(pem: string): ArrayBuffer {
  const base64 = pem
    .replace(/-----BEGIN PRIVATE KEY-----/, "")
    .replace(/-----END PRIVATE KEY-----/, "")
    .replace(/\s/g, "");
 
  const binary = atob(base64);
  const buffer = new ArrayBuffer(binary.length);
  const view = new Uint8Array(buffer);
  for (let i = 0; i < binary.length; i++) {
    view[i] = binary.charCodeAt(i);
  }
  return buffer;
}
 
export { generateAppStoreToken };

Fetching Reviews

// src/appstore/reviews.ts
 
interface Review {
  id: string;
  rating: number;
  title: string;
  body: string;
  reviewerNickname: string;
  territory: string;
  createdDate: string;
}
 
async function fetchReviews(
  appId: string,
  token: string,
  limit = 20
): Promise<Review[]> {
  const url = new URL(
    `https://api.appstoreconnect.apple.com/v1/apps/${appId}/customerReviews`
  );
  url.searchParams.set("limit", String(limit));
  url.searchParams.set("sort", "-createdDate");
  url.searchParams.set("filter[rating]", "1,2,3"); // Low ratings get priority
 
  const response = await fetch(url.toString(), {
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
  });
 
  if (\!response.ok) {
    const error = await response.text();
    throw new Error(`App Store Connect API error: ${response.status} - ${error}`);
  }
 
  const data = (await response.json()) as {
    data: Array<{
      id: string;
      attributes: {
        rating: number;
        title: string | null;
        body: string;
        reviewerNickname: string;
        territory: string;
        createdDate: string;
      };
    }>;
  };
 
  return data.data.map((item) => ({
    id: item.id,
    rating: item.attributes.rating,
    title: item.attributes.title ?? "",
    body: item.attributes.body,
    reviewerNickname: item.attributes.reviewerNickname,
    territory: item.attributes.territory,
    createdDate: item.attributes.createdDate,
  }));
}
 
export { fetchReviews };

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Architecture and full implementation of an AI-drafts / human-approves review pipeline, battle-tested across a 50M+ download portfolio of iOS and Android apps
Crashlytics integration pattern that auto-branches reply tone based on whether a crash is newly detected, fixed-pending-release, or already shipped
Confidence-threshold tuning, JWT clock-skew workarounds, and Google Play quota realities — the kind of judgment data you can't get from official docs
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Business2026-07-02
Protecting Ad eCPM in Your Rork Max App: Designing ATT Pre-Permission Priming
For iOS apps built with Rork Max, ad revenue swings heavily on your ATT opt-in rate. Here is how to design a pre-permission priming screen, implement it in SwiftUI, measure the opt-in rate, and order AdMob init correctly.
Business2026-07-01
Before You Reach for Tap to Pay on iPhone in a Rork Max App — Eligibility, Choosing a Payment Provider, and a Realistic Design
Implementation notes for judging, from a business angle, whether you can put Tap to Pay — accepting in-person payments on an iPhone alone — into a Rork Max app. Covers the organization-account and entitlement requirements, why you need a payment provider (PSP), the split between ProximityReader and the PSP, and a realistic path for an indie developer, told candidly.
Business2026-06-30
Holding App Store Messages Until the Right Moment
Price-increase consent, billing-issue, and win-back messages from the App Store appear right at launch by default. Here is how to take control of StoreKit 2 Messages and defer them until a moment that does not interrupt onboarding or checkout, with working Swift.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →