開発

GitLab APIの使い方:トークン認証とcurl例・ページネーションとレート制限の実装【2026年11月】

GitLab APIの使い方:トークン認証とcurl例・ページネーションとレート制限の実装【2026年11月】

GitLab APIは、プロジェクトやIssue、マージリクエスト、パイプラインをHTTPで読み書きするための窓口で、REST API v4とGraphQL APIの2系統という構成になっているAPIです。この記事では、アクセストークンをPRIVATE-TOKENヘッダーで渡す認証から始めて、curlでのIssue作成とパイプライン起動、1ページ20件の既定を超えて全件を取るページネーション、python-gitlabでの実装、GitLab.comのレート制限と429への備えまでを順に示します。仕様と数値は2026年11月時点の公式ドキュメントにもとづいています。

まとめ:GitLab APIはPRIVATE-TOKENで認証しLinkヘッダーで全件を取る

REST APIの呼び出しはhttps://ホスト名/api/v4/に続くパスに、トークンをPRIVATE-TOKENヘッダーで付けるだけです。読み取りだけならread_apiスコープで足り、IssueやMRを作るときにapiへ広げます。401はトークンが無効、403はスコープか権限の不足と覚えておくと、エラーの切り分けが速くなります。

一覧系は既定で20件、最大100件しか返りません。全件を取るには応答のLinkヘッダーのnextをたどります。1万件を超える一覧では件数ヘッダーが返らないため、対応するエンドポイントではキーセット方式への切り替えが必要です。GitLab.comでは認証済みAPIが1ユーザー毎分2,000件までで、超えると429とRetry-Afterが返るので、その秒数だけ待って再送する処理を最初から入れておきます。

GitLab APIの3つの窓口:REST API v4・GraphQL・glabの比較

REST API v4のベースURLとid・iid・URLエンコードしたパスの指定方法

REST APIのドキュメントによると、リクエストはGitLabのホスト名に/api/v4を続けたパスで送ります。GitLab.comならhttps://gitlab.com/api/v4/projects、Self-Managedなら自社のホスト名に置き換えます。v4は後方互換性のない変更があるときだけ上がり、新しいエンドポイントの追加先は、引き続き同じv4です。

つまずきやすいのは識別子です。プロジェクトはIDの数値か、グループ名/プロジェクト名のパスで指定でき、パスを使うときは/を%2Fに置き換えます。IssueやMRには全体で一意のidと、プロジェクト内の連番iidがあり、URLに入れるのは画面に表示されるiidのほうです。プロジェクトID 42のIssueでidが46、iidが5なら、/projects/42/issues/5が正しく、/issues/46は404になります。

RESTとGraphQLとglab apiの使い分け:取得件数とクエリ上限で選ぶ基準

呼び出し口は3つあります。迷ったらRESTから始めます。

呼び出し口 エンドポイント 向く用途 主な上限
REST API v4 /api/v4/… 一覧取得と更新全般 1ページ最大100件
GraphQL API /api/graphql 関連データを1回で取る 複雑度250・30秒
glab api RESTとGraphQL両方 手元での試行と調査 REST・GraphQLに準ずる

GraphQL APIのドキュメントでは、認証済みのクエリ複雑度が250、1ページ100ノード、クエリ本文10,000文字、リクエストのタイムアウトが30秒と定められています。MRとその承認者とパイプラインの状態をまとめて取りたいならGraphQLが向き、単純な一覧や作成ならRESTのほうが書きやすいです。両者の一般的な違いはREST APIとGraphQLの違いと選び方で整理しています。

glab apiはGitLab公式CLIのサブコマンドで、リポジトリ内で実行すると:fullpathや:idが現在のプロジェクトに置き換わり、--paginateで全ページを自動で取得します。CLIでのリリース作成はGitLab Releaseの作り方(glabで自動作成する手順)でも使っています。

GitLab APIのトークン認証:PRIVATE-TOKENヘッダーとスコープの選び方

個人・プロジェクト・グループのトークンをヘッダーで渡すcurlの設定例

REST API authenticationで推奨されている渡し方はPRIVATE-TOKENヘッダーです。Authorization: Bearerヘッダーでも同じトークンが通り、OAuthのトークンはこちらで渡します。トークンの種類の選び方と発行手順はGitLabのアクセストークン(種類の選び方と発行手順)で扱っているので、ここでは発行済みのトークンを使う前提で進めます。

# トークンは環境変数から読む。シェル履歴やスクリプトに直接書かない
export GITLAB=https://gitlab.example.com/api/v4
export GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx

# 認証できているかを確認する(自分のユーザー情報が返れば成功)
curl --silent --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" "${GITLAB}/user" | jq '{id, username}'

# Bearer ヘッダーでも同じ結果になる
curl --silent --header "Authorization: Bearer ${GITLAB_TOKEN}" "${GITLAB}/user" | jq '.username'

古い記事には?private_token=のようにURLのクエリでトークンを渡す例が残っていますが、URLはプロキシやサーバーのアクセスログに残ります。ヘッダーで渡す形にそろえます。ヘッダーでの認証の一般的な考え方はBearer Tokenの書き方とJWTとの違いを参照してください。

401・403 insufficient_scopeの判別とCIジョブトークン認証

認証まわりのエラーは、ステータスコードで原因の当たりが付きます。Troubleshooting the REST APIの一覧から、API利用の初期に出やすいものを抜き出すと次のとおりです。

コード よくある原因 確認すること
401 トークンの期限切れ・失効・打ち間違い トークンの有効期限と値
403 スコープ不足か操作権限の不足 本文のinsufficient_scope
404 パスの誤りか閲覧権限なし %2Fのエンコードとiid
429 レート制限の超過 Retry-Afterの秒数

403でスコープが足りない場合は、応答にerror="insufficient_scope"が入り、本文とWWW-Authenticateヘッダーに通るスコープが列挙されます。列挙されたもののどれか1つをトークンに足せば通ります。404は「存在しない」と「見る権限がない」を区別しないため、非公開プロジェクトでトークンのロールが足りないときも404です。ロールごとに許される操作はGitLabの権限(ロール):Guest〜Ownerの操作範囲と保護ブランチの設定手順で確かめられます。

CIジョブの中からAPIを呼ぶなら、トークンを発行せずにJOB-TOKEN: ${CI_JOB_TOKEN}ヘッダーで認証できます。ただし使えるエンドポイントは一部だけという制約付きです。ジョブでの変数の渡し方はGitLab CI/CDのrules・needs・変数の設定例にまとめています。

curlで試すGitLab APIの基本操作:プロジェクト取得からIssue作成まで

プロジェクト一覧とIssue一覧をcurlとjqで絞り込むGETリクエストの例

最初の一歩は、自分が参加しているプロジェクトの一覧です。Projects APIのmembership=trueを付けないと、GitLab.comでは公開プロジェクト全体が対象になり、目的のものが見つかりません。

# 参加しているプロジェクトのIDとパスを100件取る
curl --silent --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
  "${GITLAB}/projects?membership=true&simple=true&per_page=100" \
  | jq -r '.[] | "\(.id)\t\(.path_with_namespace)"'

# パスで1件を指定する(/ は %2F に置き換える)
curl --silent --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
  "${GITLAB}/projects/example-group%2Fapp" | jq '{id, default_branch}'

# 未完了のbugラベル付きIssueを、11月以降に更新されたものに絞る
curl --silent --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
  "${GITLAB}/projects/42/issues?state=opened&labels=bug&updated_after=2026-11-01T00:00:00Z&per_page=100" \
  | jq -r '.[] | "#\(.iid) \(.title)"'

Issues APIのlabelsはカンマ区切りで、複数指定すると「すべてのラベルを持つIssue」に絞られます。いずれかに一致させたい場合は、ラベルごとに分けて呼んでから手元で結合します。

IssueとパイプラインをPOSTで作成するリクエストボディとラベル・変数の指定

作成系はapiスコープが必要です。パラメータはクエリでも送れますが、本文が長くなるとURLの長さ制限で414が返るため、JSONの本文で送る形にしておくと後で困りません。

# Issueを作る(title は必須。存在しないラベル名を渡すと新しく作られる)
curl --silent --request POST \
  --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{"title":"夜間バッチの失敗を調査","description":"2026-11-12 02:00 のジョブが失敗","labels":"bug,batch","due_date":"2026-11-20"}' \
  "${GITLAB}/projects/42/issues" | jq '{iid, web_url}'

# main ブランチでパイプラインを起動し、変数 DEPLOY_ENV を渡す
curl --silent --request POST \
  --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{"ref":"main","variables":[{"key":"DEPLOY_ENV","value":"staging"}]}' \
  "${GITLAB}/projects/42/pipeline" | jq '{id, status, web_url}'

Pipelines APIではrefだけが必須で、variablesはkeyとvalueを持つ配列です。variable_typeを省いた場合の渡し方は環境変数です。存在しないラベル名が自動で作られる仕様は便利ですが、打ち間違いがそのままラベルとして増えるので、ラベル名はスクリプト側で固定の一覧から選ばせます。Issues APIでIssueをまとめて登録するスクリプト例はGitLabのIssue(テンプレート・ラベル・ボードの設定例とAPIでの一括登録)で扱っています。MRをAPIで作る場合の承認ルールとの関係はGitLabのマージリクエストの承認ルール設定で確認できます。

ページネーションで全件を取る実装:オフセット方式とキーセット方式

per_page最大100・Linkとx-next-pageによるbash全件取得例

一覧系のAPIは、何も指定しないとpage=1・per_page=20で返ります。per_pageの最大は100で、それより多い件数は複数回に分けて取ります。応答ヘッダーの構成はx-next-page・x-total・x-total-pagesと、rel="next"などを含むLinkです。公式ドキュメントはURLを自分で組み立てずLinkをたどるよう勧めています。

#!/usr/bin/env bash
# 未完了Issueを全件取得して1つのJSON配列にまとめる(オフセット方式)
set -euo pipefail
PID=42
page=1
rm -f page_*.json

while [ -n "${page}" ]; do
  curl --silent --fail --dump-header headers.txt \
    --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
    --output "page_${page}.json" \
    "${GITLAB}/projects/${PID}/issues?state=opened&per_page=100&page=${page}"
  # 最後のページでは x-next-page が空になり、ループを抜ける
  page=$(grep -i '^x-next-page:' headers.txt | tr -d '\r' | awk '{print $2}')
done

jq -s 'add' page_*.json > issues.json
jq 'length' issues.json

このスクリプトは、ページ数が多くない社内リポジトリならそのまま動きます。GitLab.comでは一部のページネーションヘッダーが返らない場合があると明記されているため、本番では次の節のようにLinkヘッダーをたどる実装に寄せます。

1万件超のx-total欠落とpagination=keysetへの切り替え条件

オフセット方式には2つの落とし穴があります。1つ目は件数で、結果が10,000件を超えるとx-total・x-total-pagesとrel="last"のリンクが返らなくなります。総ページ数を先に計算してから回す実装では、ここが停止する箇所です。2つ目は深さで、Self-Managedでは管理者がオフセットの上限を変えられるため、環境によっては後ろのページが取れません。

対策はキーセット方式です。pagination=keysetを付け、order_byとsortを必ず指定します。2026年11月時点で対応しているのはプロジェクト一覧(order_by=idのみ)、グループ一覧、プロジェクトのジョブ、監査イベント、リポジトリツリー(17.1以降)、プロジェクトのIssue(18.3以降)などです。次のページへのURLはLinkヘッダーにだけ入り、最後のページでLinkが消えます。

import os
import time
import requests

GITLAB = "https://gitlab.example.com/api/v4"
session = requests.Session()
session.headers["PRIVATE-TOKEN"] = os.environ["GITLAB_TOKEN"]


def get_all(url, params):
    """Link ヘッダーの next をたどって全件を返す。429 は Retry-After だけ待って再送する"""
    items = []
    while url:
        r = session.get(url, params=params, timeout=30)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "60")))
            continue
        r.raise_for_status()
        items.extend(r.json())
        url = r.links.get("next", {}).get("url")
        params = None  # next の URL にクエリが含まれるので 2 回目以降は付けない
    return items


projects = get_all(
    f"{GITLAB}/projects",
    {"pagination": "keyset", "order_by": "id", "sort": "asc", "per_page": 100, "membership": "true"},
)
print(len(projects))

キーセット方式に対応していないエンドポイントでも、このget_allはオフセット方式のLinkをたどってそのまま動きます。件数が万単位になりうる一覧だけキーセットのパラメータを付ける、という使い分けで十分です。

GitLab APIのPython実装:python-gitlabとrequestsの選択

python-gitlab 8.6系のiterator=Trueによる大量データ取得例

Pythonで書くなら、公式のREST APIをそのまま包んだpython-gitlabが手早い選択肢です。2026年11月時点のpython-gitlabのドキュメントは8.6系で、list()を引数なしで呼ぶと最初の1ページしか返らない点が注意書きされています。

import os
import gitlab  # pip install python-gitlab

gl = gitlab.Gitlab(
    "https://gitlab.example.com",
    private_token=os.environ["GITLAB_TOKEN"],
    per_page=100,
)
gl.auth()  # 認証を確かめる。失敗すると例外になる

project = gl.projects.get("example-group/app")

# iterator=True は必要な分だけ順にページを取る。件数が多い一覧はこちらを使う
for issue in project.issues.list(state="opened", labels=["bug"], iterator=True):
    print(issue.iid, issue.title)

# 全件をリストで受け取りたいときは get_all=True
mrs = project.mergerequests.list(state="merged", get_all=True)
print(len(mrs))

キーセット方式はgitlab.Gitlab(url, token, pagination="keyset", order_by="id", per_page=100)のようにクライアント生成時に指定します。ドキュメントには、執筆時点で動くのはorder_by="id"だけと書かれています。

python-gitlabとrequestsの直書きのどちらを選ぶかは、触るリソースの数で決めます。Issue・MR・パイプラインなど複数のリソースをまたぐなら、型と例外が整っているpython-gitlabのほうが保守が楽です。1〜2本のエンドポイントを定期的に叩くだけなら、依存を増やさずに前節のget_allで足ります。

レート制限と429への備え:GitLab.comの上限値とRetry-Afterでの再試行

GitLab.com認証済みAPI毎分2,000件・Groups一覧毎分50件の現行上限

GitLab.com rate limitsに載っている、2026年11月時点で適用中の上限のうちAPIに関わるものは次のとおりです。

対象 上限
認証済みAPI(ユーザーごと) 毎分2,000件
未認証(IPアドレスごと) 毎分500件
プロジェクト一覧 10分で2,000件
グループ内のプロジェクト一覧 毎分600件
単一プロジェクトの取得 毎分400件
グループ一覧 毎分50件
プロジェクト・グループの作成 1日200件

表の一覧系はそれぞれGET /api/v4/projects、GET /api/v4/groups/:id/projects、GET /api/v4/projects/:id、GET /api/v4/groupsへの呼び出しです。全体の毎分2,000件より先に、個別のエンドポイントの上限に当たります。特にグループ一覧は毎分50件と低く、グループごとにループして呼ぶ棚卸しスクリプトはすぐ429になる構造です。上限を超えると429とともにRetry-After(秒)とRateLimit-ResetTimeが返るので、前節のコードのようにその秒数だけ待ちます。注意したいのは、Projects・Groups・Users APIの制限では残量を示すヘッダーが付かず、残量があるように見えても429が返る場合があることです。残量ヘッダーを見て速度を落とす実装だけでは防げないため、429を受けたら待つ処理は必ず入れます。

Self-Managedの上限は管理者が設定するもので、GitLab.comの値は当てはまりません。自社サーバーで構築する場合の全体像はGitLabとは(構成要素・3つの提供形態)で確認できます。

2027年1月発効予定のプラン別上限とFreeのバースト毎分100件の影響

同じページには、プランごとの新しい上限が提案として載っています。認証済みユーザーの持続上限は1時間あたりFree 5,000件・Premium 15,000件・Ultimate 25,000件で、1分あたりのバースト上限はFreeのAPIが100件・Premiumが1,250件・Ultimateが2,000件です。発効はPremiumとUltimateで2027年1月が予定されており、それまでの間に一時的に有効にする期間を設けると案内されています。

現行の上限との低いほうが適用されるため、Freeで毎分数百件を投げているスクリプトは、新上限の適用後に止まる可能性があります。今のうちに、一覧はキーセット方式でまとめて取る、同じデータを繰り返し取らずに手元に保存する、という2点で呼び出し回数を減らしておきます。数値と発効時期は変わりうるので、運用前に同ページで最新の値を確認してください。

GitLab APIで自動化を組む判断:自前スクリプトで済む場面と見送る場面

自前のAPIスクリプトで足りる条件とWebhook・CIジョブへ寄せるケース

自前でAPIを定期的に呼ぶスクリプトが向くのは、集計や棚卸しのように「決まった時刻に一覧を取って加工する」処理です。週次のIssue集計、期限切れトークンの洗い出し、パイプラインの失敗率の算出などは、この記事のコードの組み合わせで作れます。

逆に、イベントに反応する処理を定期実行の差分チェックで作るのは避けます。「MRが作られたら通知する」「パイプラインが失敗したらチケットを起票する」を1分ごとのポーリングで実装すると、呼び出し回数がレート制限を圧迫し、検知も最大1分遅れます。この場合の切り替え先は、WebhookでGitLabから通知を受ける形か、パイプラインの中のジョブでAPIを呼ぶ形です。リポジトリの内容に応じた処理なら、CIジョブの中でCI_JOB_TOKENを使うのが手間も漏えいの危険も最小です。AIエージェントからGitLabを操作したいだけなら、自前でAPIを包まずにGitLab MCPサーバーの接続設定を使うほうが早く済みます。

社内システム連携まで広げるときに設計で詰まる箇所と外部委託の判断

GitLabのIssueを社内のチケット管理や工数管理、顧客向けポータルとつなぐ段階になると、論点はAPIの呼び方から設計に移ります。詰まりやすいのは、どちらを正とするかの同期方向、二重登録を防ぐ外部IDの持ち方、トークンを人ではなくサービスアカウントに持たせる権限設計、429や障害で欠けたデータの再取得の4点です。ここを決めずにスクリプトを増やすと、退職者のトークンで動く連携や、どこにも記録が残らない同期処理が残ります。

連携先が2つ以上ある、もしくは止まると業務が止まる連携なら、最初に同期の設計を文書にしてから作ることを勧めます。一創のAPI開発・システム連携では、GitLabを含む既存システムのAPI仕様を確認したうえで、同期方式とエラー時の再処理まで含めた連携の設計と実装を引き受けています。

よくある質問

GitLab APIを使い始める段階でよく出る質問をまとめました。

GitLab APIは無料プランでも使えますか?

利用可能です。REST APIとGraphQL APIはGitLab.comのFreeでも、Self-ManagedのCommunity Editionでも利用できます。ただし、APIで操作できるのはそのプランで使える機能に限られ、たとえばIssue作成時に担当者を配列で渡すassignee_idsは、Issues APIの表ではPremium以上の属性という位置付けです。GitLab.comのFreeではプロジェクト・グループアクセストークンが使えないため、自動化用のトークンは個人トークンかサービスアカウントで用意します。

GitLab APIのトークンにはどのスコープを付ければよいですか?

一覧の取得や参照だけならread_apiで足ります。Issue・MR・パイプラインの作成や更新をするならapiが必要です。GraphQLもクエリはread_api、ミューテーションはapiです。権限が足りないと403とinsufficient_scopeが返り、応答に必要なスコープが示されるので、それを見て足します。

プロジェクトIDはどこで確認できますか?

プロジェクトのトップページ右上にある縦三点リーダー(Actions)からCopy project IDを選ぶとコピーできます。APIで調べるならGET /projects/グループ名%2Fプロジェクト名の応答のidを見ます。IDの代わりにURLエンコードしたパスをそのまま使うこともできますが、プロジェクトの移動や名前変更でパスは変わるため、長く動かすスクリプトでは数値のIDを使うほうが安全です。

1回のリクエストで100件より多く取得できますか?

できません。REST APIのper_pageの上限は100です。全件が必要な場合はLinkヘッダーのnextをたどって複数回に分けて取得します。CLIで済ませるならglab api issues --paginateで全ページを自動取得でき、--output ndjsonを付けるとjqで1件ずつ処理できます。

GitLab APIのv3は使えますか?

使えません。現在のREST APIはv4で、公式ドキュメントにv3の記載はありません。/api/v3を使う例は、古い版のドキュメントを公開し続けているサーバーや過去の解説記事に残っているものです。GitLabは後方互換性を壊す変更があるときだけメジャーバージョンを上げ、新しい機能は同じv4に追加していきます。古い記事のコードを使うときは、パスのv3と、クエリでトークンを渡す書き方の2点を直してから動かします。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.11.11 テックブログ Microsoft 2026年11月の月例更新:Outlookの悪用済みCVE-2026-124194と修正版の確認方法
  2. 2026.01.21 コラム 税務調査の録音は禁止?判例・裁決と国税局マニュアルで見る、断られたときの対応
  3. 2026.11.11 テックブログ JANISの不正アクセス:最大11,652名の医療機関情報と情報窃取プログラムの検知設定
  4. 2026.11.11 テックブログ SMBC日興証券の不正アクセス:持株会会員最大5.6万人の範囲と受託システムの分離設計
  5. 2025.10.24 テックブログ AWS障害の確認方法と直近の障害一覧|2026年の中東・us-east-1障害と原因・対策

RELATED POSTS 関連記事

目次