GitHub API 使い方入門:Octokit認証・レート制限・ページネーション
GitHub APIをOctokitで叩く実践入門。PATとfine-grained tokenの違い、REST/GraphQLの使い分け、レート制限とページネーション、issue/PR操作をコピペで動くコードで解説。
「PR一覧を取るだけ」のスクリプトのつもりでした。
ところが本番リポジトリで動かしたら、返ってきたのは30件。実際のオープンPRは120件あったんです。残りの90件はどこに消えたのか。
答えは「消えてなかった」。GitHub APIは一覧を勝手にページ分割して返すので、僕は1ページ目だけ読んで「全部読んだ」と思い込んでいただけでした。古いPRが見えないまま「最近は静かだな」と判断していたわけです。怖い話です。
GitHub APIは、issueもPRもコミットもリポジトリ設定も、全部コードから触れる公式の入口です。便利なんですが、最初につまずくポイントが決まっています。認証(どのtokenを使う?)、ページネーション(何件まで返ってくる?)、レート制限(いつ止められる?)。この3つで事故る人がほとんどです。
この記事では、その3つを中心に、Octokit(GitHub公式のクライアント)でissue/PR/コミットを叩く実際のコードまで、僕が踏んだ地雷込みで書きます。
この記事の要点
- 認証は3種類。手元の検証はPersonal Access Token、自動化はfine-grained token、組織や複数リポジトリはGitHub App。「とりあえずclassic tokenの全権限」は事故のもと。
- RESTとGraphQLは使い分ける。単体操作(issue作る、PRにlabel付ける)はREST、関連情報をまとめて1回で取る集計はGraphQL。
- 一覧は必ずページネーションする。1ページ最大100件。
octokit.paginate()を使えば全ページを自動でたどれる。 - レート制限は即リトライしない。認証ありで毎時5,000、GraphQLは別枠。
x-ratelimit-resetを見て待つ。 - Webhookは「APIで取りに行く」のと逆。GitHub側から飛んでくるイベントを受ける仕組みで、署名検証が必須。受信側の実装はClaude CodeでWebhookを実装する手順にまとめた。
GitHub APIって、結局どこから触るのか
GitHub APIは https://api.github.com というURLにHTTPリクエストを投げるだけのものです。curl でも fetch でも叩けます。たとえばオープンなissueを取るなら、こうです。
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/octocat/Hello-World/issues?state=open"
これで動きます。動くんですが、実際に自動化を組むと、ページネーションのループ、レート制限の待機、エラー時のリトライ、型——同じ定型コードを毎回書くことになります。それを肩代わりしてくれるのがOctokitです。GitHubが公式に出しているクライアントで、JavaScript版(@octokit/rest)が一番よく使われます。
Octokitを使うと、さっきのcurlがこうなります。
npm install octokit
import { Octokit } from "octokit";
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const { data } = await octokit.rest.issues.listForRepo({
owner: "octocat",
repo: "Hello-World",
state: "open",
});
console.log(data.map((issue) => issue.title));
ヘッダーもAPIバージョンも、Octokitが面倒を見てくれます。生の fetch で書く理由は「依存を増やしたくない最小スクリプト」くらいで、自動化を育てるならOctokitに乗ったほうが後が楽です。
認証:tokenの選び方で9割決まる
GitHub APIで一番事故るのは、認証まわりです。tokenはパスワードと同じなのに、サンプルコードやログにベタ書きされがちなんですね。まず種類を整理します。
| 認証方式 | 向いている場面 | 権限の絞り方 | 寿命 |
|---|---|---|---|
| Personal Access Token (classic) | 古い手順・一部の旧機能 | scope単位(粗い) | 任意・無期限も可 |
| Fine-grained PAT | 個人の自動化・特定リポジトリ | リポジトリ+権限単位(細かい) | 最大1年・短命推奨 |
| GitHub App | 組織・複数リポジトリ・配布 | App権限+インストール単位 | installation tokenは1時間 |
僕のおすすめは「fine-grained tokenを第一候補にする」です。classic tokenの repo scopeは便利なんですが、自分のアカウント配下のprivateリポジトリにまるごと届いてしまう。issueを読むだけのスクリプトに、全リポジトリのコードを書き換える権限を持たせる——これがどれだけ怖いかは、漏れたときを想像すればわかります。
fine-grainedなら「このリポジトリの Issues: Read and write だけ」のように刻めます。issue triageなら Issues: Read and write、PR一覧を見るだけなら Pull requests: Read-only、コミット履歴やファイル読み取りなら Contents: Read と Metadata: Read から始める。足りなくなったら足すのが鉄則で、最初から盛らない。
組織で複数人・複数リポジトリにまたがるならGitHub Appです。Appはユーザー個人に紐づかないので、退職者のtokenが生き残る事故が起きません。installation tokenが1時間で切れるのも、漏れたときの被害を勝手に小さくしてくれる安全装置です。発行手順はGitHub Apps公式ドキュメントが一次情報です。
そしてどの方式でも共通のルールがひとつ。token値を絶対に表示しない。console.log(process.env) も、リクエスト全体のdumpも、CIのdebug logも危険です。Octokitに渡すのは必ず環境変数から。
// NG: ベタ書き。コミット履歴に永遠に残る
const octokit = new Octokit({ auth: "github_pat_11ABC..." });
// OK: 環境変数から読む。値はどこにも出さない
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
if (!process.env.GITHUB_TOKEN) throw new Error("GITHUB_TOKEN を設定してください");
REST と GraphQL、どっちを使う?
GitHub APIにはRESTとGraphQLの2系統があります。最初は迷うところなので、判断基準をはっきりさせます。
RESTは「1つのことを1回やる」のが得意です。「このリポジトリのオープンissueを一覧する」「このPRにlabelを付ける」「コミットを作る」みたいな、リソース単位の操作。URL・HTTPメソッド・ステータスコードが素直で、追いやすい。最初の自動化はRESTで組むのが安全です。
GraphQLは「関連する情報をまとめて1回で取る」のが得意です。「直近100件のPRと、その作者と、レビュー状態と、付いているlabelを、ひとつの表にしたい」みたいな集計。RESTだと「PR一覧 → 各PRのレビュー → 各PRのlabel」と何度も往復しますが、GraphQLは欲しいフィールドだけを1クエリで取れます。
| 観点 | REST API | GraphQL API |
|---|---|---|
| 向く作業 | issue作成、PR更新、コミット作成などの単体操作 | 複数リポジトリ・関連情報をまとめた集計 |
| 学習コスト | URLとメソッドで追える | クエリ設計を覚える必要がある |
| レート制限 | 毎時5,000リクエスト(認証あり) | 別枠・クエリの「ポイント」で計算 |
| 落とし穴 | ページネーション漏れ、権限不足 | クエリ肥大化、取得フィールドの過不足 |
レート制限の数え方が違う点に注意してください。RESTは「リクエスト回数」、GraphQLは「クエリの複雑さに応じたポイント」で計算されます。GraphQLで一度に深く取りすぎると、回数は1回でも重いコストになります。詳しい上限はREST APIのレート制限に書いてあります。
結論としては、RESTで安全な基本形を作り、集計が増えてきたらGraphQLを足す。この順番で困りません。
ページネーション:冒頭の「90件消えた」を防ぐ
僕がやらかしたやつです。GitHubの一覧APIは、結果をページに分けて返します。1ページは最大100件(per_page=100)。それを超えたら次のページを読まないと、残りは存在しないことになってしまう。
Octokitには octokit.paginate() があって、これが全ページを自動でたどってくれます。手書きで Link ヘッダーを解析するループを書く必要はありません。
import { Octokit } from "octokit";
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
// 全ページを自動でたどって、全PRを配列で返す
const allPulls = await octokit.paginate(octokit.rest.pulls.list, {
owner: "octocat",
repo: "Hello-World",
state: "open",
per_page: 100, // 1ページあたりを最大に
});
console.log(`オープンPRは全部で ${allPulls.length} 件`);
これで冒頭の「30件しか取れない」は起きません。per_page: 100 を付けても、120件あれば内部で2ページ取りに行ってくれます。
逆に言うと、octokit.paginate() を使わずに octokit.rest.pulls.list() を直接呼ぶと、デフォルトの最初の数十件しか返りません。「件数で判断するコード」(古いPRの棚卸し、release noteの生成など)でこれをやると、静かに間違えます。エラーも出ない。だから一番たちが悪い。一覧を扱うときは反射的に paginate を使う、と体に入れておくのがいいです。
レート制限:403を見ても、すぐ叩き返さない
レート制限は、短時間に叩きすぎたときの制限です。認証ありのRESTなら毎時5,000リクエスト。GraphQLは別枠です。
ここでありがちな事故が、403 や 429 が返ってきたときに while (true) で即リトライすることです。制限中にさらに叩くので、制限がもっと延びる。自分で自分の首を絞めます。
正しいのは、レスポンスヘッダーを見て待つことです。x-ratelimit-remaining(残り回数)と x-ratelimit-reset(回復するUNIX時刻)を見れば、いつまで待てばいいかわかります。下は、コピペで動く「現在のレート制限を確認して、残りが少なければ回復まで待つ」スクリプトです。
// scripts/check-rate-limit.mjs
import { Octokit } from "octokit";
if (!process.env.GITHUB_TOKEN) {
throw new Error("GITHUB_TOKEN を設定してください");
}
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// REST の現在のレート制限状況を取得
const { data } = await octokit.rest.rateLimit.get();
const core = data.resources.core;
console.log(`残り ${core.remaining} / ${core.limit} 回`);
console.log(`回復: ${new Date(core.reset * 1000).toLocaleTimeString()}`);
// 残りが10回を切ったら、回復時刻まで待つ(即リトライしない)
if (core.remaining < 10) {
const waitMs = core.reset * 1000 - Date.now() + 1000;
if (waitMs > 0 && waitMs <= 10 * 60 * 1000) {
console.log(`残り少。${Math.ceil(waitMs / 1000)} 秒待ちます…`);
await sleep(waitMs);
console.log("回復しました。処理を続行できます。");
}
}
実は octokit.paginate() を使っていれば、Octokitが内部でレート制限のヘッダーをある程度見て待ってくれます。それでも大量処理を回すなら、上のように自分で残量を確認してから走り出すほうが安全です。「処理対象を小分けにする」「CIのスケジュールをずらす」も効きます。
Octokitでissue・PR・コミットを操作する
ここまでは読み取り中心でした。書き込みも見ておきます。ただし順番が大事で、いきなり書き込みを自動化しない。最初はdry-run(対象を表示するだけ)で人間が確認し、APPLY=true のときだけ実際に書き込む。この境界を引くだけで事故率が激減します。
// scripts/triage-issues.mjs
import { Octokit } from "octokit";
const { GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO, APPLY } = process.env;
if (!GITHUB_TOKEN || !GITHUB_OWNER || !GITHUB_REPO) {
throw new Error("GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO を設定してください");
}
const octokit = new Octokit({ auth: GITHUB_TOKEN });
const owner = GITHUB_OWNER;
const repo = GITHUB_REPO;
// 1. 全オープンissueを取得(PRは除外)
const allIssues = await octokit.paginate(octokit.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
});
const issues = allIssues.filter((issue) => !issue.pull_request);
// 2. 再現手順が無さそうなissueを「needs-repro候補」とする
const candidates = issues.filter(
(issue) => !/再現|repro|steps/i.test(issue.body ?? ""),
);
console.log(`対象 ${candidates.length} 件(全 ${issues.length} 件中)`);
for (const issue of candidates) {
console.log(` #${issue.number} ${issue.title}`);
}
// 3. APPLY=true のときだけ label を付ける(既定は dry-run)
if (APPLY === "true") {
for (const issue of candidates) {
await octokit.rest.issues.addLabels({
owner,
repo,
issue_number: issue.number,
labels: ["needs-repro"],
});
console.log(` → #${issue.number} に needs-repro を付与`);
}
} else {
console.log("dry-run。実行するには APPLY=true を付けてください。");
}
PRの作成は octokit.rest.pulls.create、PRへのコメントは octokit.rest.issues.createComment(PRも内部的にはissue扱いなので注意)、コミットの取得は octokit.rest.repos.listCommits、新規コミットの作成は octokit.rest.git.createCommit です。メソッド名はRESTのエンドポイントとほぼ1対1で対応しているので、GitHub REST APIドキュメントのエンドポイント名から逆引きできます。
Claude Codeにこういうスクリプトを書かせるときは、依頼文に制約を先に入れます。「tokenは process.env から読む・ログに出さない」「一覧は必ず octokit.paginate を使う」「更新処理はdry-runを既定にし、APPLY=true のときだけ実行」「20件を超える更新は止めてsummaryだけ出す」。やってほしいことよりやってはいけないことを先に渡すのがコツです。
API と Webhook は「取りに行く」か「飛んでくる」かの違い
最後によく混同される点を。ここまでのAPIは、こちらから「取りに行く」「書きに行く」やり方でした(ポーリング)。一方**Webhookは、GitHub側からイベントが「飛んでくる」**仕組みです。PRが開かれた瞬間、issueが作られた瞬間に、GitHubがあなたのサーバーにPOSTしてくる。
使い分けはシンプルです。「5分ごとに状態をまとめたい」「過去分を集計したい」ならAPIで取りに行く。「開かれた瞬間に反応したい」ならWebhookで受ける。リアルタイム性が要るかどうかで決めます。
| GitHub API(ポーリング) | Webhook | |
|---|---|---|
| 向き | こちら → GitHub に取りに行く | GitHub → こちら に飛んでくる |
| タイミング | 自分で叩いたとき | イベント発生の直後 |
| 向く用途 | 定期集計・棚卸し・過去分の処理 | 即時の通知・自動triage |
| 注意 | レート制限・ページネーション | 署名検証・重複配信 |
Webhookには固有の落とし穴が2つあります。ひとつは署名検証。x-hub-signature-256 を検証しないと、GitHub以外から送られた偽のJSONを本物として処理してしまいます。もうひとつは冪等性(べきとうせい:同じ処理を2回やっても結果が壊れない性質)。GitHubは同じイベントを再送することがあるので、x-github-delivery のIDを記録して二度処理しないようにします。
この受信サーバーの実装(Expressでの署名検証と重複排除)は長くなるので、別記事に分けました。Claude CodeでWebhookを実装する手順に、コピペで動くサーバーごと置いてあります。ローカルスクリプトからCIまでつなげたいなら、Git workflow実践ガイドとGitHub Actions高度活用も合わせてどうぞ。
よくある質問
Q. classic tokenとfine-grained token、結局どっちを使えばいい? 新規ならfine-grainedです。リポジトリと権限を細かく刻めて、最大1年で期限が切れます。classicは一部の旧機能でしか使えない場面に限り、使うなら短命にしてください。
Q. レート制限の上限はいくつ? 認証ありのRESTで毎時5,000リクエスト(GitHub App installationやEnterpriseはさらに多い)。未認証だとIPあたり毎時60まで激減します。GraphQLは回数ではなくポイント計算で別枠です。
Q. ページネーションは毎回 octokit.paginate を使うべき?
一覧系は基本そうしてください。件数で判断するコード(棚卸し・集計・release note生成)で1ページ目だけ読むと、エラーも出ずに静かに間違えます。1件だけ取る・最新1件だけでいい、と確信できる場合のみ単発呼び出しでOKです。
Q. RESTとGraphQLは混ぜていい? 混ぜて問題ありません。単体操作はREST、まとめて取りたい集計はGraphQL、と用途で選べば自然と併用になります。
Q. tokenをうっかりコミットしてしまった。どうする? すぐにそのtokenをGitHubの設定画面でrevoke(無効化)し、新しいtokenを発行し直してください。履歴から消すより先に無効化が最優先です。GitHubのSecret scanningが先に検知して自動失効させることもあります。
実際に試した結果
冒頭の「90件消えた」事件以来、僕がGitHub API自動化で最初に書くのは、本処理ではなく octokit.rest.rateLimit.get() と octokit.paginate() の2行になりました。順番でいうと、(1) fine-grained tokenを最小権限で発行 → (2) paginate で全件取れているか件数を目視 → (3) dry-runで更新候補を出す → (4) APPLY=true で限定的に書き込む。Webhookやスケジュール実行はその後です。
この型に落としてから、「取れていると思ったら取れていなかった」も「レート制限で止まって無限リトライ」も起きなくなりました。GitHub APIは賢いコードを書く競技ではなく、認証を絞り、全件取り、止められたら待つという地味な土台がそのまま品質になります。まずは上の triage-issues.mjs をdry-runで動かして、自分のリポジトリで件数を確かめるところから始めてみてください。
手元の型を増やしたいならテンプレート教材に実用スクリプトをまとめています。自社リポジトリ向けに権限設計から組みたい場合はClaude Code研修・導入相談からどうぞ。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
制作会社がClaude Codeに触らせる前に決める権限チェックリスト
クライアントサイトを壊さずにAI編集を使うための、制作会社向け権限と確認の型です。
SaaSサポートのバグ報告をClaude Codeで再現手順に変える実務フロー
問い合わせ文をそのまま開発へ投げず、再現手順、証拠、次の一手に整えるサポート向け手順です。
Obsidianの古いメモをClaude Codeの指示書に変える10分ルーチン
Obsidianに溜めたメモが毎回ゴミになる人へ。事実・決定・未確認に仕分けして、Claude Codeがそのまま動ける指示書に変える朝の10分の型を紹介します。