gpc-sdk-usage について
gpc-sdk-usageは、TypeScript開発者向けに@ gpc-cli/apiと@gpc-cli/authを使ったGoogle Playへのプログラムアクセスを案内するスキルです。認証やクライアント作成に加え、公開や運用の一般的な操作を扱います。
主な機能
- 01サービスアカウント認証の設定
- 02型付きAPIクライアントの作成
- 03アプリの編集とリリース管理
- 04レビュー、定期購入、購入の処理
- 05ページネーションとレート制限
活用シーン
- 01TypeScriptサービスからGoogle Playのリリースを管理
- 02スクリプトでアプリレビューを取得・返信
- 03バックエンドでアプリ内購入を検証
- 04既存アプリにPlay操作を組み込む
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yasserstudio/ gpc-skills --skill gpc-sdk-usageインストール後、Claude Code で/gpc-sdk-usageを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/api-reference.mdauth-patterns.mdscripts/detect_gpc.mjs詳細
gpc-sdk-usage
@ gpc-cli/api と @gpc-cli/auth をスタンドアロンの TypeScript SDK パッケージとして使用し、Google Play にプログラムからアクセスします。
使用する場面
- Google Play と連携するバックエンドサービスを構築する場合
- カスタムダッシュボードや自動化スクリプトを作成する場合
- TypeScript/JavaScript からプログラムでリリースを管理する場合
- 型付き API クライアントを直接使用する場合(CLI 経由ではなく)
- Google Play の操作をより大きなアプリケーションに統合する場合
必要な入力
- Node.js 20+ と TypeScript 5+
- @gpc-cli/api および @gpc-cli/auth パッケージ
- サービスアカウントキー — JSON ファイルまたは生の JSON 文字列
手順
0. パッケージをインストールする
npm install @gpc-cli/api @gpc-cli/auth
これらはスタンドアロンパッケージのため、CLI 全体をインストールする必要はありません。
1. 認証する
import { resolveAuth } from "@gpc-cli/auth";
// From file path
const auth = await resolveAuth({
serviceAccountPath: "/path/to/key.json",
});
// From JSON string (e.g., from environment variable)
const auth = await resolveAuth({
serviceAccountJson: process.env.PLAY_SA_KEY,
});
// From environment (GPC_SERVICE_ACCOUNT or GOOGLE_APPLICATION_CREDENTIALS)
const auth = await resolveAuth();
Read: references/auth-patterns.md で、高度な認証パターンとトークンのキャッシュについて確認してください。
2. API クライアントを作成する
import { createApiClient } from "@gpc-cli/api";
const client = createApiClient({
auth,
maxRetries: 3,
timeout: 30_000,
});
このクライアントを使用すると、Android Publisher v3、Play Developer Reporting v1beta1、Play Custom App Publishing v1(v0.9.56+)、Games Configuration v1configuration(v0.9.86+)の各 API にわたる、Google Play Developer API の全 234 エンドポイントに型付きでアクセスできます。
2a. Enterprise クライアントを作成する(v0.9.56+)
Play Custom App Publishing API を使用して非公開アプリを公開する場合は、別のファクトリーを使用してください。
import { createEnterpriseClient, type CustomApp } from "@gpc-cli/api";
const enterprise = createEnterpriseClient({ auth });
const app: CustomApp = await enterprise.apps.create(
"1234567890", // developer account ID (int64, from Play Console URL)
"./app.aab", // bundle path
{
title: "My Private App",
languageCode: "en_US",
organizations: [{ organizationId: "customer-org-id" }],
},
);
console.log("Assigned package name:", app.packageName);
// com.google.customapp.A1B2C3D4E5 (Google-assigned, you cannot influence)
注意:
- 非公開アプリは恒久的に非公開です。一度作成すると、公開アプリにはできません。
- 作成後の操作(バージョンのアップロード、トラック、掲載情報)には、返された
packageNameを使用して通常のcreateApiClient()を使います。 - Play Console で、サービスアカウントに「create and publish private apps」権限が必要です。
- 基盤となる
HttpClient.uploadCustomApp<T>(path, filePath, metadata, contentType)メソッドは、初回のセッション開始 POST で JSON メタデータを送信するマルチパートの再開可能なアップロードを処理します。他の Google API でもこのパターンを再利用する方法については、ResumableUploadOptions.initialMetadataを参照してください。
CLI での同等の操作とセットアップ全体の手順については、gpc-enterprise スキルを参照してください。
Read: references/api-reference.md で、すべての名前空間とメソッドを含むクライアント API の全容を確認してください。
3. 編集のライフサイクル
Google Play の多くの操作では、編集セッションが必要です。
const APP = "com.example.app";
// 1. Create an edit
const edit = await client.edits.insert(APP);
// 2. Make changes within the edit
const tracks = await client.tracks.list(APP, edit.id);
const details = await client.details.get(APP, edit.id);
// 3. Validate before committing
await client.edits.validate(APP, edit.id);
// 4. Commit the edit (applies all changes)
await client.edits.commit(APP, edit.id);
// Optional: commit with options (v0.9.51+)
await client.edits.commit(APP, edit.id, {
changesNotSentForReview: true,
changesInReviewBehavior: "HALT_REVIEW",
});
重要: 同時に開ける編集は 1 つだけです。編集は必ずコミットするか削除してください。
4. よく使われるパターン
カスタムのクローズドテストトラックを作成する(v0.9.79+)
// Create a custom closed testing track before uploading to it
const track = await client.edits.tracks.create(packageName, "my-custom-track");
リリースを割り当てる前に、カスタムトラックを作成する必要があります。作成後は、標準の client.tracks.update() 呼び出しを使用して、新しいトラックにリリースを送信します。
リリースをアップロードする
const edit = await client.edits.insert(APP);
// Upload the bundle
const bundle = await client.bundles.upload(APP, edit.id, "app-release.aab");
// Upload with device tier config (v0.9.51+)
const bundle2 = await client.bundles.upload(APP, edit.id, "app-release.aab", {
deviceTierConfigId: "my-tier-config",
});
// Set the track
await client.tracks.update(APP, edit.id, "beta", {
track: "beta",
releases: [{
versionCodes: [bundle.versionCode],
status: "completed",
releaseNotes: [
{ language: "en-US", text: "Bug fixes and improvements" },
],
}],
});
// Commit
await client.edits.validate(APP, edit.id);
await client.edits.commit(APP, edit.id);
レビューを一覧表示して返信する
// No edit needed for reviews
const reviews = await client.reviews.list(APP, {
maxResults: 50,
translationLanguage: "en",
startIndex: 0, // pagination offset (v0.9.51+)
});
for (const review of reviews.reviews ?? []) {
if (review.comments?.[0]?.userComment?.starRating <= 2) {
await client.reviews.reply(APP, review.reviewId, "Thanks for the feedback!");
}
}
サブスクリプションを管理する
// No edit needed for subscriptions
const subs = await client.subscriptions.list(APP);
// Get a specific subscription
const sub = await client.subscriptions.get(APP, "premium_monthly");
// Update with mutation options (v0.9.51+)
await client.subscriptions.update(APP, "premium_monthly", data, "price", {
allowMissing: true,
latencyTolerance: "PRODUCT_UPDATE_LATENCY_TOLERANCE_LATENCY_TOLERANT",
});
// Activate a base plan
await client.subscriptions.activateBasePlan(APP, "premium_monthly", "monthly");
購入を検証する
// No edit needed for purchases
const purchase = await client.purchases.getProduct(APP, "coins_100", purchaseToken);
if (purchase.purchaseState === 0 && purchase.acknowledgementState === 0) {
await client.purchases.acknowledgeProduct(APP, "coins_100", purchaseToken);
}
難読化解除ファイルをアップロードする(v0.9.51+)
// Upload ProGuard mapping
await client.deobfuscation.upload(APP, edit.id, versionCode, "mapping.txt", "proguard");
// Upload native debug symbols
await client.deobfuscation.upload(APP, edit.id, versionCode, "symbols.zip", "nativeCode");
拡張ファイルを管理する(v0.9.51+)
// Get expansion file info
const obb = await client.expansionFiles.get(APP, edit.id, versionCode, "main");
// Upload a new expansion file
const uploaded = await client.expansionFiles.upload(APP, edit.id, versionCode, "main", "main.obb");
// Patch expansion file references
await client.expansionFiles.patch(APP, edit.id, versionCode, "main", {
referencesVersion: 10,
});
ページネーションを使用して一回限りの商品を一覧表示する(v0.9.51+)
const products = await client.oneTimeProducts.list(APP, {
pageSize: 25,
pageToken: nextToken,
});
5. ページネーション
組み込みのページネーションユーティリティを使用して、大量の結果を処理します。
import { paginate, paginateAll } from "@gpc-cli/api";
// Async generator (stream results)
for await (const page of paginate(
(token) => client.subscriptions.list(APP, { pageToken: token }),
{ limit: 100 },
)) {
for (const sub of page) {
console.log(sub.productId);
}
}
// Collect all results
const all = await paginateAll(
(token) => client.subscriptions.list(APP, { pageToken: token }),
);
v0.9.83 の新機能:
paginateAllが実際の継続トークンを返すようになりました。そのため、--limitと--next-pageを組み合わせると、レビュー、ユーザー、購入、IAP、サブスクリプションの取得を正しく再開できます。すべての CLI の一覧表示コマンドで、統一された{ <key>, nextPageToken, meta.count, message? }JSON エンベロープも共有されます(v0.9.87 では grants、testers、tracks にも拡張)。一覧表示コマンドが返す配列を直接読み込んでいるスクリプトは動作しなくなります。キー付きフィールドを分割代入するように更新してください。
v0.9.98:
client.users.list()は常にpageSize=-1を送信します。これは Google が受け付ける唯一の値です(「Pagination is not currently available」)。呼び出し元が指定したpageSizeは無視され、型定義でも非推奨です。一覧全体が 1 回のレスポンスで返されます。
6. レート制限
v0.9.47 以降、createApiClient() は Google の 6 バケットモデル(各バケットで 1 分あたり 3,000 クエリ)を使用して、すべての API 呼び出しに自動でレート制限を適用します。手動で設定する必要はありません。
// Rate limiting is automatic — all calls are throttled by resource type
const client = createApiClient({ auth });
// Buckets: edits, purchases, reviews, reporting, monetization, default
レート制限をカスタマイズする場合(たとえば、複数のプロセス間でクォータを共有する場合):
import { createRateLimiter, RATE_LIMIT_BUCKETS } from "@gpc-cli/api";
// Override specific buckets
const limiter = createRateLimiter([
{ ...RATE_LIMIT_BUCKETS.edits, maxTokens: 1500 }, // Half of default
{ ...RATE_LIMIT_BUCKETS.purchases, maxTokens: 1500 },
]);
const client = createApiClient({ auth, rateLimiter: limiter });
resolveBucket(path) 関数は、API パスをバケットに自動で割り当てます。
/edits/パス →editsバケット/purchases/、/orders→purchasesバケット/reviews→reviewsバケット- Reporting API →
reportingバケット /subscriptions、/oneTimeProducts、/inappproducts→monetizationバケット- その他すべて →
defaultバケット
7. エラー処理
import { PlayApiError } from "@gpc-cli/api";
import { AuthError } from "@gpc-cli/auth";
try {
await client.edits.insert(APP);
} catch (error) {
if (error instanceof AuthError) {
console.error(`Auth failed: ${error.code}`);
} else if (error instanceof PlayApiError) {
console.error(`API error ${error.status}: ${error.code}`);
console.error(`Suggestion: ${error.suggestion}`);
}
}
変更履歴の生成(v0.9.62+)
gpc changelog generate の変更履歴パイプラインは、スタンドアロンの @gpc-cli/core エクスポートとして公開されています。クラスタリング/リント済みのデータ構造を直接利用したい CI ツールに便利です。
import {
generateChangelog,
resolveLocales,
renderPlayStore,
PLAY_STORE_LIMIT, // 500
type LocaleBundle,
type GeneratedChangelog,
} from "@gpc-cli/core";
const generated: GeneratedChangelog = await generateChangelog({
from: "v0.9.61",
to: "HEAD",
});
// GitHub target: three renderers exposed as RENDERERS["md" | "json" | "prompt"]
// Play Store target: resolveLocales + renderPlayStore
const locales = await resolveLocales("en-US,fr-FR,de-DE");
const { output, bundle } = renderPlayStore(generated, {
locales,
format: "json",
});
for (const entry of bundle.locales) {
console.log(`${entry.language}: ${entry.chars}/${entry.limit} (${entry.status})`);
}
--locales auto の場合、resolveLocales の第 2 引数として { client, packageName } を渡します。これにより、client.listings.list が呼び出され、公開中の Play ストア掲載情報からロケールの一覧が推定されます。
リリースノートをドラフトに適用する(v0.9.64+)
import {
applyReleaseNotes,
validateBundleForApply,
bundleToReleaseNotes,
waitForBundleProcessing,
} from "@gpc-cli/core";
// Convert a LocaleBundle to the API shape
const releaseNotes = bundleToReleaseNotes(bundle);
// Validate (returns blocked locale errors, if any)
const errors = validateBundleForApply(bundle);
if (errors.length > 0) throw new Error(errors.join(", "));
// Write into the latest draft on a track
await applyReleaseNotes(client, "com.example.app", "production", releaseNotes);
// waitForBundleProcessing (v0.9.64+, extended v0.9.77): polls bundles.list
// after AAB upload with Fibonacci backoff (2s, 3s, 5s, 8s, 13s, 21s, 34s ~86s)
// until the uploaded versionCode appears. Fixes large-AAB race.
// v0.9.77 also adds multi-retry guard on validate/commit (15s, 30s, 45s).
await waitForBundleProcessing(client, "com.example.app", editId, versionCode);
設定型の VitalsThresholds(v0.9.82+)
VitalsThresholds が、@gpc-cli/config で公開される型付き設定の一部になりました。
import type { GpcConfig } from "@gpc-cli/config";
const config: GpcConfig = {
vitals: {
thresholds: { crashRate: 2.0 },
},
};
VitalsThresholds は ResolvedConfig(実行時に完全にマージされた形式)にも含まれています。GPC 設定ファイルをプログラムから読み書きするツールの構築にご利用ください。
Orders の OfferPhaseDetails(v0.9.79+)
Orders のフラットな offerPhase 文字列フィールドは非推奨です。代わりに offerPhaseDetails を参照してください。
const order = await client.purchases.orders.get(APP, orderId);
// Deprecated: order.offerPhase
// Preferred:
const phase = order.offerPhaseDetails; // OfferPhaseDetails — phase type, cycle counts, pricing
download() の指数バックオフ(v0.9.80+)
APK/AAB バイナリのダウンロードに使用する client.download() で、request() と同じ再試行動作に合わせて、指数バックオフによる自動再試行が行われるようになりました。コードを変更する必要はありません。一時的な 5xx エラーやネットワークのタイムアウトは、自動的に再試行されます。
Play GCS バケットからの一括レポート(v0.9.93+)
Play の月次一括レポートは、Publisher API リソースではなく、デベロッパーアカウントにリンクされた Cloud Storage バケット内の CSV オブジェクトです。**v0.9.93 の破壊的変更:**機能していなかった client.reports.list は @gpc-cli/api から削除され、古い downloadReport エクスポートは @gpc-cli/core から削除されました。代わりに、新しい @gpc-cli/core エクスポート 3 つを使用してください。
import { resolveAuth, DEFAULT_SCOPES, STORAGE_READ_ONLY_SCOPE } from "@gpc-cli/auth";
import {
listReports,
downloadStatsReport,
downloadFinancialReport,
resolveReportsBucket,
parseMonth,
type ListReportsResult,
} from "@gpc-cli/core";
// The reports path needs one extra scope. Request it only here: storage-scoped tokens are
// cached under a separate key, so other commands' tokens never carry storage access.
const auth = await resolveAuth({
serviceAccountPath: "key.json",
scopes: [...DEFAULT_SCOPES, STORAGE_READ_ONLY_SCOPE],
});
// pubsite_prod_<developerId>, or an explicit reports.bucket value
const bucket = resolveReportsBucket({ developerId: "1234567890" });
const month = parseMonth("2026-02");
const { reports, nextPageToken }: ListReportsResult = await listReports(
auth,
bucket,
"installs",
{ packageName: "com.example.app", month, maxResults: 50 },
);
// Stats: per app, one CSV per dimension (reviews reports have no dimension)
const stats = await downloadStatsReport(
auth, bucket, "com.example.app", "installs", month, "country",
);
console.log(stats.objectName, stats.csv); // gunzipped, UTF-16 decoded to UTF-8
// Financial: account-level, ZIP archives
const fin = await downloadFinancialReport(auth, bucket, "earnings", month);
if (fin.kind === "csv") {
console.log(fin.text);
} else {
for (const entry of fin.entries) console.log(entry.name, entry.csv.length);
}
auth は ReportsAuth({ getAccessToken(): Promise<string> })を満たすだけでよいため、任意のトークンソースを使用できます。失敗時には REPORT_* コードを持つ GpcError がスローされます(gpc-troubleshooting を参照してください)。初回実行時に発生するのは REPORT_ACCESS_DENIED で、Play Console でサービスアカウントに「アプリ情報の表示と一括レポートのダウンロード(読み取り専用)」を付与するまで発生します。
API の正確性に関する最近の変更履歴
- v0.9.57:
apprecovery.cancel/deployの URL が複数形の/appRecoveries/を使用するようになりました。dataSafety.updateはPUTではなくPOSTです。実在しないdataSafety.getは削除されました。onetimeproducts.offers.activateOffer/deactivateOfferが追加されました。新しいgetVitalsErrorCount関数が追加されました。 - v0.9.58/v0.9.59: Vitals LMK のメトリクスセットは
lmkRateMetricSetで、メトリクスはuserPerceivedLmkRate、userPerceivedLmkRate7dUserWeighted、userPerceivedLmkRate28dUserWeighted、distinctUsersです。(v0.9.58 では誤ったリソース名でリリースされ、v0.9.59 で修正されました。)
検証
resolveAuth()が有効な認証クライアントを返すcreateApiClient({ auth })が動作するクライアントを作成するclient.edits.insert(APP)が正常に編集を開始する- API 呼び出しが型付きレスポンスを返す
- エラー処理が
PlayApiErrorとAuthErrorを捕捉する
障害モード/デバッグ
| 症状 | 考えられる原因 | 解決方法 |
|---|---|---|
AUTH_NO_CREDENTIALS | 認証情報の取得元が見つかりません | serviceAccountPath を渡すか、GPC_SERVICE_ACCOUNT を設定します |
AUTH_INVALID_KEY | キーファイル内の JSON が不正です | Google Cloud Console から再ダウンロードします |
| 編集内容の挿入が 403 で失敗する | サービスアカウントに API アクセス権がありません | GCP で Google Play Developer API を有効にします |
| 同時編集の競合 | 別の編集が開かれています | 先に既存の編集をコミットするか、削除します |
ステータス 429 の PlayApiError | レート制限に達しています | 適切なバケットを設定して createRateLimiter() を使用します |
| 型を解決できない | TypeScript の設定が誤っています | moduleResolution: "bundler" または "node16" が設定されていることを確認します |
client.reports.list が関数ではない | v0.9.93 で削除されました(以前から動作していませんでした) | @gpc-cli/core の listReports/downloadStatsReport/downloadFinancialReport を使用します |
レポートの呼び出しで REPORT_ACCESS_DENIED が返される | サービスアカウントに一括レポートの権限がありません | Play Console で「アプリ情報の表示と一括レポートのダウンロード(読み取り専用)」を有効にしてから、再試行します |
| 権限を付与してもレポートの呼び出しで 403 が返される | devstorage.read_only を指定せずにトークンが発行されています | resolveAuth に scopes: [...DEFAULT_SCOPES, STORAGE_READ_ONLY_SCOPE] を渡します |
関連スキル
- gpc-setup — サービスアカウントの作成と認証設定
- gpc-plugin-development — SDK を内部で使用するプラグインの構築
- gpc-troubleshooting — API エラーコードの解釈