RORK LABEN
FUNDING — Rorkの$15MシードはLeft Lane Capital主導で、Peak XV・True Ventures・Goodwater・a16z Speedrunが参加していますGROWTH — Rorkは月間74.3万訪問・成長率85%と、利用の伸びが続いていますMAX — Rork MaxはネイティブSwiftアプリを生成し、iPhone・iPad・Watch・TV・Vision Pro・iMessageに対応しますMAX — HealthKit・Core ML・Dynamic Islandなど、React Nativeでは届きにくい領域に踏み込めますMARKET — AppleもXcode 27でエージェント型コーディングを推進し、AIがネイティブ開発を担う流れが加速していますMARKET — Gartnerは2026年末までに新規アプリの75%が低コード/ノーコード製になると予測していますFUNDING — Rorkの$15MシードはLeft Lane Capital主導で、Peak XV・True Ventures・Goodwater・a16z Speedrunが参加していますGROWTH — Rorkは月間74.3万訪問・成長率85%と、利用の伸びが続いていますMAX — Rork MaxはネイティブSwiftアプリを生成し、iPhone・iPad・Watch・TV・Vision Pro・iMessageに対応しますMAX — HealthKit・Core ML・Dynamic Islandなど、React Nativeでは届きにくい領域に踏み込めますMARKET — AppleもXcode 27でエージェント型コーディングを推進し、AIがネイティブ開発を担う流れが加速していますMARKET — Gartnerは2026年末までに新規アプリの75%が低コード/ノーコード製になると予測しています
記事一覧/開発ツール
開発ツール/2026-07-03上級

App Store Connect API の日次集計が月末に合わなくなるとき — JWT 失効・レポート確定遅延・突合の運用メモ

App Store Connect API で自動化した売上・レビュー収集は、止まらずに「欠け」ます。401/404/429 が生む3種類の静かな欠損を fetch ログと突合クエリで検出し、バックフィルで埋める運用設計を実装コード付きでまとめます。

App Store Connect API3JWT2Sales Report自動化12Supabase33レート制限3バックフィルRork481

プレミアム記事

月初に App Store Connect の管理画面を開いて、Supabase に貯めてきた日次売上の合計と見比べたとき、数字が 3% ほど合いませんでした。調べると、30日のうち3日分の売上行が丸ごと欠けていました。

Slack への日次レポートは毎朝欠かさず届いていました。だからパイプラインは健全だと思い込んでいたのです。実際には、特定の日のレポート取得だけが静かに失敗し、その日の行が存在しないまま月が締まっていました。

自動化した集計は「止まる」より「欠ける」ほうが厄介です。止まれば通知の沈黙で気づけますが、欠けは通知が届き続ける限り見えません。この運用メモでは、App Store Connect API(以下 ASC API)で売上・レビュー収集を自動化したときに実際に踏んだ欠損のパターンと、それを検出・修復する仕組みを実装コードごと残します。

欠損は3つの顔でやってくる

ひと月分の実行ログを洗い直すと、欠損の原因は3つに分類できました。

症状HTTPステータス根本原因欠け方
認証切れ401JWT の失効・クロックずれ・.p8 の改行消失その実行回の全データ
レポート未確定404Apple 側の日次レポート生成が遅れている特定日の売上行だけ
取りこぼし429ページネーション中のレート制限レビューの後半ページだけ

厄介なのは、この3つがすべて「例外を握りつぶしても翌日は普通に動く」性質を持つことです。リトライなしの実装では、失敗した日の穴だけが残ります。

まず前提の整理です。ASC API は全リクエストに ES256 署名の JWT を要求し、トークンの有効期限は最大20分です。Sales and Trends API は日次レポートを .tsv.gz で返しますが、前日分が確定するのは太平洋時間の早朝で、日本時間だと当日夜〜翌日昼にずれ込むことがあります。そしてレート制限の具体値は公開されていません。この3つの仕様が、それぞれ 401・404・429 の欠損に対応します。

JWT は「毎回作る」より「寿命を計測する」

最初の実装では、失敗時のログに「401 だった」としか残していませんでした。これでは、トークンが古かったのか、鍵の読み込みが壊れたのか、区別がつきません。

トークンに発行時刻を持たせてキャッシュし、失敗時には「トークン年齢」をログに残すようにしたところ、原因の切り分けが一気に楽になりました。

// supabase/functions/_shared/asc-token.ts
import { create } from "https://deno.land/x/djwt@v3.0.2/mod.ts";
 
const ISSUER_ID = Deno.env.get("ASC_ISSUER_ID")!;
const KEY_ID = Deno.env.get("ASC_KEY_ID")!;
const PRIVATE_KEY_PEM = Deno.env.get("ASC_PRIVATE_KEY")!;
 
let cached: { token: string; issuedAt: number } | null = null;
 
// 15分で更新(上限20分に対して5分のバッファ)
const REFRESH_AFTER_MS = 15 * 60 * 1000;
 
export async function getAscToken(): Promise<string> {
  const now = Date.now();
  if (cached && now - cached.issuedAt < REFRESH_AFTER_MS) {
    return cached.token;
  }
 
  const pemBody = PRIVATE_KEY_PEM
    .replace("-----BEGIN PRIVATE KEY-----", "")
    .replace("-----END PRIVATE KEY-----", "")
    .replace(/\s/g, "");
  const binaryKey = Uint8Array.from(atob(pemBody), (c) => c.charCodeAt(0));
 
  const privateKey = await crypto.subtle.importKey(
    "pkcs8",
    binaryKey,
    { name: "ECDSA", namedCurve: "P-256" },
    false,
    ["sign"],
  );
 
  const nowSec = Math.floor(now / 1000);
  const token = await create(
    { alg: "ES256", kid: KEY_ID, typ: "JWT" },
    {
      iss: ISSUER_ID,
      iat: nowSec,
      exp: nowSec + 1140, // 19分。上限より1分短くしてクロックずれを吸収
      aud: "appstoreconnect-v1",
    },
    privateKey,
  );
 
  cached = { token, issuedAt: now };
  return token;
}
 
/** 401 が返ったときに、トークンが古かったのか判定するための材料 */
export function tokenAgeMs(): number | null {
  return cached ? Date.now() - cached.issuedAt : null;
}

exp を上限ぴったりの1200秒ではなく1140秒にしているのは、実行環境と Apple 側のクロックずれで「発行直後なのに期限切れ扱い」になる事故を避けるためです。Edge Functions のようなコールドスタートのある環境では、鍵のインポートに時間がかかった分だけ iat と実リクエストの間が開くので、この1分の余白が効きます。

もうひとつ、401 の定番の注意点が .p8 の改行消失です。シークレットストアに貼り付けた際に改行が失われると、PEM のパースは通るのに署名だけが壊れる、という分かりにくい壊れ方をします。対処として、デプロイ直後に一度だけ /v1/apps を叩いて 200 を確認するスモークテストを、デプロイ手順に固定で入れておくのが確実でした。ローカルで通って本番だけ落ちる場合は、まずここを疑うのが早道です。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
401・404・429 が引き起こす3種類の欠損を切り分け、fetch ログテーブルで「取れたはずの日」を機械的に検出する設計
Sales API の 404 を「データなし」と確定させず、レポート確定遅延を吸収する 72 時間バックフィルの実装コード
generate_series を使った週次突合 SQL と、レビュー収集の重複・再分析コストを抑える upsert パターン
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-03-30
Rork × Trigger.dev でモバイルアプリのバックグラウンドジョブを自動化する方法
Rork で開発したモバイルアプリに Trigger.dev を組み合わせ、プッシュ通知・データ同期・コンテンツ更新などのバックグラウンドジョブを自動化する実践的な実装ガイドです。
開発ツール2026-06-23
ゲーミフィケーションでDAUは増えたのに継続率が動かなかった — Rork運用で効いた設計の立て直し
ポイント・バッジ・リーダーボードを入れるとDAUは上がりますが、継続率は別物です。サーバー権威のポイント台帳、燃え尽きないストリーク、新規を萎えさせないリーグ設計まで、Rork運用で効いた立て直しを実装コード付きで整理します。
開発ツール2026-05-26
App Store Connect・RevenueCat・AdMob の朝の指標を Cloudflare Workers で1本化する — 6アプリ運用の日次集約アーキテクチャ
Rork で複数アプリを運用する個人開発者向けに、App Store Connect API・RevenueCat REST・AdMob Reporting API を Cloudflare Workers Cron で集約し、毎朝1通の日次レポートに統合する実装手順をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →