Sakiのプログラミング学習ブログ

プログラミングについて学んだことや、学習の振返りを書いています。

【REST】curlコマンドでGitHubAPIをたたく方法

はじめに

 フィヨルドブートキャンプ(以下、FBC)の「自分で考えたサービスをリリースする」という最終課題で、GitHubREST APIを利用した。(※近日リリース予定)
外部サービスのAPIをたたくのは今回が初めてで、

  • いきなりラッパーライブラリ*1を使ってAPIをたたこうとしたら、使い方が全く分からなかった
  • GitHubが用意してくれているSinatraアプリAPIをたたこうとしたら、認証の話などが出てきて大混乱

という事態に陥った。
API,ラッパーライブラリ,OAuthアプリケーションによる認証など、分からないことが同時に複数出てきて、どのようにサービス開発のための技術検証を進めていけばいいか悩んだので、FBCの卒業生の方々に相談したところ、

「認証の仕組み」と、「APIから欲しい情報を取得できるか」は分けて考えたほうが良い。
認証の理解は難しいので、まず欲しいデータを取れるかどうかを、認証を行うOAuthアプリケーションではなく、ターミナル上でcurlコマンドを使って試すといい。

とアドバイスをいただいた。
なので、「自分が欲しい情報を、そもそもGitHub APIが持っているか」 を検証するために、curlコマンドで様々なAPIリクエストを試した。

この記事の内容

当時curlコマンドでAPIをたたいた時につまづいたことを中心に、

  • REST と GraphQL の違い・特徴
  • curlコマンドを使ったAPIのたたき方
  • 「GitHubAPIに対してやりたいこと」を実現できるエンドポイントの探し方
  • 認証した上でAPIリクエストする方法
  • 今自分が許可されているリクエスト回数の上限の確認方法
  • クエリパラメーターの使い方

についてまとめた。

目次

GitHubAPIを使うと、どんなことができるのか?

GitHubAPIを使うと、

  • Issueの作成・更新・取得・削除
  • Organizationsの一覧やメンバーの取得
  • Pull Requestの生成・更新・取得・削除
  • Repositoryの生成・更新・取得・削除

などを行うことができる。できることは多岐に渡るので、この他にもたくさんある。

REST API と GraphQLのちがい

GitHubが用意しているAPIには、RESTとGraphQLの2種類がある。

REST

欲しいデータが入っているAPIのエンドポイント*2を指定してデータを得る。
自分が欲しいデータ以外もごっそり入っているので、アプリケーション開発等で特定の情報だけ使いたい場合、コードを書いて欲しい情報だけ得られるように加工する必要がある。

特徴

  • クエリの実行:URLで欲しい情報を要求する(クエリを実行する)
  • 欲しい情報が入っているエンドポイントを指定する
  • データ形式:受け取るデータ形式は決まっている
  • 欲しいデータ以外も取得してしまう

使用例

例えば、とあるユーザーのGitHubアカウント名とアイコン画像のURLを取得したい場合、以下のようなエンドポイントを指定してデータを得ることになる。(参考:ユーザー - GitHub Docsより)

# Saki-htrというユーザーのpublicな情報を取得する
$ curl https://api.github.com/users/Saki-htr

{
  "login": "Saki-htr", # アカウント名
  "id": 58052292,
  "node_id": "MDQ6VXNlcjU4MDUyMjky",
  "avatar_url": "https://avatars.githubusercontent.com/u/58052292?v=4", # アイコン画像のURL
  "gravatar_id": "",
  "url": "https://api.github.com/users/Saki-htr",
  "html_url": "https://github.com/Saki-htr",
  "followers_url": "https://api.github.com/users/Saki-htr/followers",
  "following_url": "https://api.github.com/users/Saki-htr/following{/other_user}",
  "gists_url": "https://api.github.com/users/Saki-htr/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/Saki-htr/subscriptions",
  "organizations_url": "https://api.github.com/users/Saki-htr/orgs",
  "repos_url": "https://api.github.com/users/Saki-htr/repos",
  "events_url": "https://api.github.com/users/Saki-htr/events{/privacy}",
  "received_events_url": "https://api.github.com/users/Saki-htr/received_events",
  "type": "User",
  "site_admin": false,
  "name": "服部紗希",
  "company": null,
  "blog": "https://saki-htr.hatenablog.com/",
  "location": "Tokyo Japan",
  "email": null,
  "hireable": null,
  "bio": null,
  "twitter_username": null,
  "public_repos": 33,
  "public_gists": 1,
  "followers": 1,
  "following": 0,
  "created_at": "2019-11-21T21:35:26Z",
  "updated_at": "2022-07-25T12:05:10Z",
  "private_gists": 16,
  "total_private_repos": 1,
  "owned_private_repos": 1,
  "disk_usage": 6885,
  "collaborators": 0,
  "two_factor_authentication": true,
  "plan": {
    "name": "free",
    "space": 976562499,
    "collaborators": 0,
    "private_repos": 10000
  }
}

このJSONデータのloginキーにアカウント名、avatar_urlキーにアイコン画像のURLが入っている。 このように、REST APIでは、欲しい情報のみを取得することはできない。

GraphQLとは

クライアント側が、必要なデータだけをAPIから取ってこれるように設計された言語。

特徴

  • GraphQLは欲しいデータと型をピンポイントで指定して、欲しい情報だけ受け取れる
  • クエリの実行:言語で欲しい情報を要求する(クエリを実行する)
  • 受け取る方の型を指定できる
  • 必要とするデータだけを取得できる

使用例

先程のRESTと同じように、とあるユーザーのGitHubアカウント名とアイコン画像のURLを取得したい場合、以下のようなクエリを書くと、アカウント名とアイコン画像だけが入ったJSONデータを取得できる。

# 実行するクエリ
query { 
  user(login: "Saki-htr") { 
    login
    avatarUrl
  }
}
# レスポンス
{
  "data": {
    "user": {
      "login": "Saki-htr",
      "avatarUrl": "https://avatars.githubusercontent.com/u/58052292?u=655c1af43b9c8331b65387923ef25d6bc197cded&v=4"
    }
  }
}

このように、欲しい情報のみを取得することができる。
公式が用意してくれている以下のページで、手軽にクエリを実行&レスポンスの確認ができる。

REST APIに、curlコマンドでリクエストする

RESTにリクエストする時の基本

REST API にリクエストを行うときは、HTTP メソッド(例:GET,POST)とパス(URL)を指定する。これに加えて、リクエストヘッダー、パス、クエリ、またはボディのパラメーターを指定することもできる。

以下に実際にcurlコマンドでたたいた時の例を書いた。

例:指定したリポジトリのissueのリストを取得する

# 書き方
curl https://api.github.com/repos/<リポジトリのオーナーになっているユーザー名>/<リポジトリ名>/issues

# 例:fjordllc/bootcampリポジトリのissue一覧を取得する
curl https://api.github.com/repos/fjordllc/bootcamp/issues

(返ってくるデータの量が膨大なので、返ってくるJSONデータを見たい方は以下のURLにアクセスをお願いします🙏)

「GitHubAPIに対してやりたいこと」を実現できるエンドポイントの探し方

以下の公式ドキュメントに一覧が載っているので、ここから探す。

ただ、この目次にはHTTPメソッドとパスしか記載されていないため、APIを触り慣れていない当初はここから探すのは難しかった。
「やりたいこと」から探したい場合、各見出しのエンドポイントをクリックすると、実現したい事柄ごとのリストのページにアクセスできる。

例えば、issueに対してやりたいことを実現するURLを探したい場合、まず「issues」という見出しをクリックする。

すると、issue - GitHub Docsというページにとぶ。

そのエンドポイントでできることが目次で載っているので、分かりやすい。

ヘッダー情報も取得したい場合

curlコマンドで-iオプションを付けると、ヘッダー情報も含めてレスポンスが返される。

$ curl -i https://api.github.com/users/Saki-htr

# レスポンスヘッダー
HTTP/2 200
server: GitHub.com
date: Sun, 06 Nov 2022 03:15:35 GMT
content-type: application/json; charset=utf-8
cache-control: public, max-age=60, s-maxage=60
vary: Accept, Accept-Encoding, Accept, X-Requested-With
etag: W/"368a3f29b158b6dcc6f93af02e427ef105cfea0a8dc8eeeed6824f664419d4dc"
last-modified: Mon, 25 Jul 2022 12:05:10 GMT
x-github-media-type: github.v3; format=json
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
access-control-allow-origin: *
strict-transport-security: max-age=31536000; includeSubdomains; preload
x-frame-options: deny
x-content-type-options: nosniff
x-xss-protection: 0
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
content-security-policy: default-src 'none'
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
x-ratelimit-reset: 1667708135
x-ratelimit-resource: core
x-ratelimit-used: 1
accept-ranges: bytes
content-length: 1340
x-github-request-id: C80B:546D:182C4C:1EA570:636726D7

# レスポンスボディ
{
  "login": "Saki-htr",
  "id": 58052292,
  "node_id": "MDQ6VXNlcjU4MDUyMjky",
  "avatar_url": "https://avatars.githubusercontent.com/u/58052292?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/Saki-htr",
  "html_url": "https://github.com/Saki-htr",
  "followers_url": "https://api.github.com/users/Saki-htr/followers",
  "following_url": "https://api.github.com/users/Saki-htr/following{/other_user}",
  "gists_url": "https://api.github.com/users/Saki-htr/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/Saki-htr/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/Saki-htr/subscriptions",
  "organizations_url": "https://api.github.com/users/Saki-htr/orgs",
  "repos_url": "https://api.github.com/users/Saki-htr/repos",
  "events_url": "https://api.github.com/users/Saki-htr/events{/privacy}",
  "received_events_url": "https://api.github.com/users/Saki-htr/received_events",
  "type": "User",
  "site_admin": false,
  "name": "服部紗希",
  "company": null,
  "blog": "https://saki-htr.hatenablog.com/",
  "location": "Tokyo Japan",
  "email": null,
  "hireable": null,
  "bio": null,
  "twitter_username": null,
  "public_repos": 33,
  "public_gists": 1,
  "followers": 1,
  "following": 0,
  "created_at": "2019-11-21T21:35:26Z",
  "updated_at": "2022-07-25T12:05:10Z"
}

REST APIのリクエスト制限

リクエスト回数には制限があるので注意

REST APIには、リクエスト回数に制限がある。条件によって、どのような制限がかかるか異なる。
詳細は公式ドキュメントのREST API のリソース - GitHub Docsで説明されている。

これまでの上述の例のように、curlコマンドで未認証でREST APIにリクエストする場合、1時間あたり最大60件しかリクエストが許可されていない。この上限を超えるとAPIにリクエストできなくなり、たたいても以下のレスポンスが返ってくるようになる。

{
  "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
  "documentation_url": "https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting"
}

現在許可されているリクエストの最大回数,残りの回数を確認する方法

-iオプションをつけた時に返ってくるヘッダーの中にリクエスト制限に関する情報がのっている。

HTTP/2 200

(...省略...)

# 1時間あたりに許可されるリクエストの最大数
x-ratelimit-limit: 60
# 現在のレート制限ウィンドウに残っているリクエストの数
x-ratelimit-remaining: 58
# 現在のレート制限ウィンドウがリセットされる時刻 (UTCエポック秒)
x-ratelimit-reset: 1667708135
# 現在のレート制限ウィンドウで行ったリクエストの数
x-ratelimit-used: 2

(...省略...)

1時間あたりのリクエスト回数を上げるには、認証が必要

1時間あたりのリクエスト回数を上げるには、GitHub API で認証を行う必要がある。
(公式:REST API の使用を開始する - GitHub Docsより)

ターミナルでcurlコマンドを使ってAPIリクエストする場合

認証方法は複数あるが、私は、公式ドキュメントで一番簡単な方法と紹介されていた、パーソナルアクセストークンによる認証を使った。

1. パーソナルアクセストークンの作成

https://github.com にログインし、https://github.com/settings/tokens にアクセスし、パーソナルアクセストークンを作成する。

具体的な手順は以下のドキュメントに載っている

2. 作成したトークンを環境変数として保存する

取得したトークンを直打ちしても認証することはできる。 が、公式ドキュメントに

これらの値は、GitHub や、その他あらゆるパブリックな場所には、決して保存しないでください。それらを 環境変数として保存することをお勧めします。 トークン用の変数を設定することで、シェルの履歴にトークンが残るのを避けることができます。
(認証の基本 - GitHub Docsより)

とあるので、環境変数として保存した。
私の場合はfishシェルを使っているので、以下のように実行することで、環境変数を設定した。

$ set <任意の環境変数名> <作成したパーソナルアクセストークン>

# 例
$ set API_TOKEN <作成したパーソナルアクセストークン>


echoコマンドで出力して設定できていることを確認する。※環境変数名の前に$を付けるのを忘れない。

$ echo $API_TOKEN
#=><作成したパーソナルアクセストークン>

3. -uオプションを使って、認証した上でAPIリクエストを実行する

curlコマンドの-uオプションを使って、ユーザー名とパーソナルアクセストークンを送信することで、GitHubAPIに認証をした上でAPIリクエストを実行できる

$ curl -u <自分のGitHubアカウントのユーザー名>:<パーソナルアクセストークンを設定した環境変数名> https://api.github.com/<リクエストしたいパス>

# 例
$ curl -u Saki-htr:$API_TOKEN https://api.github.com/repos/fjordllc/bootcamp/issues


-iオプションも合わせて使い、ヘッダーを確認することで、リクエスト回数の上限が増えたことが確認できる。

$ curl -u Saki-htr:$API_TOKEN https://api.github.com/repos/fjordllc/bootcamp/issues

(...省略...)

# 1時間あたりに許可されるリクエストの最大数
x-ratelimit-limit: 5000 ✅60→5000に増えている!
# 現在のレート制限ウィンドウに残っているリクエストの数
x-ratelimit-remaining: 4997
# 現在のレート制限ウィンドウがリセットされる時刻 (UTCエポック秒)
x-ratelimit-reset: 1667710840
# 現在のレート制限ウィンドウで行ったリクエストの数
x-ratelimit-used: 3

(...省略...)

アプリケーションからAPIリクエストする場合

 自分のPCからではなく、自分が開発しているアプリケーションから認証させてAPIリクエストを行いたい場合は、パーソナルアクセストークンではなく、APIリクエストさせたいアプリケーションをOAuthアプリとして登録する必要がある。

OAuthアプリの登録方法は以下のドキュメントに詳しく載っている。

OAuthアプリの作成 - GitHub Docs

クエリパラメーターの使い方

各エンドポイントには、そのエンドポイントで使えるクエリパラメーターがある。何が使えるかは、公式ドキュメントの各エンドポイントのドキュメントに載っている。

以下は、Pulls (List pull requests)- GitHub Docsのドキュメント。

「Query parameters」の下に、使えるクエリパラメーターが載っている

このエンドポイントにAPIリクエストすると、指定したリポジトリのPullRequestの一覧が取得できるが、もっと条件をしぼって取得したいとする。
そのような場合、用意されているクエリパラメーターを使って以下のように、取得するPullRequestを絞ることができる。

# stateがclosed状態になっているPR
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed"

# 更新された順にPRが返ってくる
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?sort=updated"

# 検索にヒットした最初の3件だけ取得する
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?per_page=3"

クエリパラメーターを使う時は、URL全体を""で囲まないと、以下のようにエラーになってしまうので、注意。

$ curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed

fish: No matches for wildcard 'https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed'. See `help wildcards-globbing`.
curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=closed
     ^

複数使いたい場合は、&で繋ぐ。

# updated順に並び替えをして、最初の3つのPRを取得する
$ curl "https://api.github.com/repos/fjordllc/bootcamp/pulls?sort=updated&per_page=3"

クエリパラメーターを使っていない時の挙動は、どこを見たら分かるのか?

そもそもクエリパラメーターを使わないでAPIリクエストした時の挙動がどうなっているかは、公式ドキュメントの各クエリパラメーターのDefault:を見ると分かる

List pull requests の Query parameters

今回の場合、Defaultは

  • state : open
  • sort: created
  • per_page:30
  • page:1

とあるので、https://api.github.com/repos/fjordllc/bootcamp/pullsにリクエストすると、

  • openになっているPR かつ
  • 作成された順に並び替え かつ
  • 1ページあたりの検索結果数が30件 かつ
  • 1ページ目

つまり、openな状態のPRを、作成された順に検索してヒットした最初の30件が返ってくることになる。

1回のリクエストでもっと多くの数を取得したい

なので、1回のリクエストでもっと多くの数を取得したい場合、per_pageで件数を指定する必要がある。最大100件まで指定することができる

per_page と page とは?

per_page と pageが少し分かりにくいので補足する。
例えば、指定したリポジトリのPRのうち、openなものが100件あるとする。
この時、

$ curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=open&per_page=40

とリクエストすると、以下のような構成になる。

  • page1: 最初の40件
  • page2: その次の40件
  • page3: その次の20件

ここで、page2の部分のPR40件を取得したい場合は、

curl https://api.github.com/repos/fjordllc/bootcamp/pulls?state=open&per_page=40&page=2

というようにpageを指定する。

おわりに

今は欲しい情報を取得できるエンドポイントをささっと調べることができるが、APIを初めてたたいた当初は、公式ドキュメントを見てもリクエストのやり方が全然分からなかったので、成長を感じて嬉しい😁

参考記事

*1:ある開発環境向けに提供されたプログラムなどを別の環境で利用できるようにするものをラッパーという。Rubyのラッパーライブラリには、GitHub公式のoctokit.rbがある。

*2:あるプログラムがAPIなどの形で外部に公開している機能の所在を示す識別名やネットワーク上のアドレス、URL/URIなどのこと。