api
StackOverflow
oauth2

StackOverflowのAPIを試してみる

More than 1 year has passed since last update.

技術的なQ&Aサイトとして確固たる地位を得つつあるStackOverflowですが、実はAPIも提供されています。かなり多くのAPIがあり、さまざまな情報を機械的にアクセスできるようになっています。もちろん、OAuth2的な認証認可を行って、その認可ユーザに関する情報を得やすい機能も提供されています。

ここでは、StackOverflowのAPIを試しに使ってみるまでの手順を紹介します。基本的に、Webブラウザとcurlコマンドしか使わないので、手軽に試せます。ちなみに、もちろんStackExchangeにアカウント登録が済んでいることが前提です。

アプリ登録

まず、Stack AppsというサイトをWebブラウザで開きます。

stackoverflow_1.png

右にあるRegister an applicationリンクを押すと、アプリ登録のためのフォームが出てきます。

stackoverflow_2.png

それぞれ必要な値を入力していきます。

  • Application Name - アプリの名称です。
  • Description - アプリの説明文です。
  • OAuth Domain - OAuthの手順で使われるドメイン名を入力します。例えばOAuth2のAuthorization Code GrantやImplicit Grantを使う場合は、redirect_uriパラメータで指定するURLのドメイン名を指定します。開発時はlocalhostでも構いません。
  • Application Website - 登録するアプリのURLを入れます。これも開発時にはhttp://localhostで構いません。
  • Application Icon - APIを試すだけなら未入力で大丈夫です。

入力したら、Register Your Applicationボタンを押して、アプリ登録を完了します。次に表示されるページでは、登録されたアプリとしてAPIを利用するための様々な情報が表示されます。

stackoverflow_3.png

認証認可

次に、APIを利用するためのアクセストークンを入手します。ここでは、OAuth2のAuthorization Code Grantによる手順を紹介します。具体的な手順は、Authenticationに書かれています。

認証認可画面の呼び出し

まず、URLを組み立てて、認証認可画面をWebブラウザで開きます。URLの構成は以下になります。

https://stackexchange.com/oauth
?client_id=[CLIENT_ID]&scope=[SCOPE]&redirect_uri=[REDIRECT_URI]&state=[STATE]
(実際には1行)
  • CLIENT_ID - 先ほどアプリ登録した結果発行されたアプリを特定するためのIDです。上記の例の場合は、6677になります。
  • SCOPE - ユーザに認可してもらう権限を空白区切りで列挙します。
  • REDIRECT_URI - StackOverflowのドメインで行われる認証認可後、アプリに戻ってくる際の戻り先URLを指定します。このURLはアプリ登録時に指定したOAuth Domainがドメイン名として含まれていなければなりません。
  • state - CSRF対策の値です。ここここに説明がありますが、本番環境では必須です。

scopeは以下が提供されています。ここでは、read_indexとprivate_infoを指定します。

  • read_inbox - ユーザのグローバルインデックスにアクセスします。
  • no_expiry - このscopeを持つアクセストークンには有効期限がありません。
  • write_access - ユーザとして書き込み処理を行います。
  • private_info - サイト上でのユーザのプライベートな行動全てにアクセスします。

redirect_uriは、アプリ登録時に指定したドメイン名を持つURLであれば、パスやクエリパラメータなどは自由です。ここでは、http://localhost/fooとしましょう。stateは本来指定しないとCSRF脆弱性を発生させてしまうのですが、今回はAPIのテスト目的なので省略します。

まとめると、以下のようになります。

https://stackexchange.com/oauth?client_id=6677&scope=read_inbox%20private_info&redirect_uri=http%3A%2F%2Flocalhost%2Ffoo

Webブラウザで上記のURLにアクセスすると、以下のような画面が表示されます。

stackoverflow_4.png

先に進むために、Approveボタンを押します。

アクセストークンの取得

Approveボタンを押した後、redirect_uriパラメータで指定したURLにリダイレクトされます。その際、クエリーパラメータとしてcodeという値が渡されてきます。

http://localhost/foo?code=[CODE]

このcode値は、認証されたユーザが指定されたscopeに関して認可を行ったことを示しています。これは一時的な値であり、このcode値と他の値を組み合わせて、StackOverflowのサーバからアクセストークンを発行してもらいます。具体的には、https://stackexchange.com/oauth/access_tokenにPOSTメソッドで以下の値を送信します。

  • client_id - アプリ登録した結果発行されたアプリを特定するためのIDです。上記の例の場合は、6677になります。
  • client_secret - アプリ登録した際にclient_idと共に発行されたClient Secret値です。
  • code - 先ほど入手したcode値です。
  • redirect_uri - 認証認可ページを呼び出した際に指定したredirect_uri値をそのまま指定します。

上記の値はapplication/x-www-form-urlencodedで送信します。curlコマンドを使って、アクセストークンの発行処理をStackOverflowのサーバに依頼します。

curl -X POST -H "Content-Type: application/x-www-form-urlencoded"
-d "client_id=6677&client_secret=[YOUR_CLIENT_SECRET]&code=[CODE]&
redirect_uri=http%3A%2F%2Flocalhost%2Ffoo"
https://stackexchange.com/oauth/access_token
(実際には1行)

実行後、発行されたアクセストークンとそのアクセストークンの有効期限が返却されます。

access_token=[ACCESS_TOKEN]&expires=86400

expires値の単位は秒です。上記の場合は、24時間有効なアクセストークンを得られたことになります。

これでAPIを利用する準備が整いました。

APIの利用

StackOverflowが提供するAPIは、基本的には全てRESTful APIです。各APIのEndpoint URLを叩くと、それに応じて処理が行われ、結果がJSON形式で返ってきます。Endpoint URLは、以下です。

https://api.stackexchange.com/2.2

各APIでは、上記のEndpoint URLに続けて、パスを追加していきます。そして、APIに応じて、HTTP Methodを使い分けていきます。今回の例では、read_indexprivate_infoというscopeでしたのでGETのみとなりますが、write_accessをscopeに含めていた場合は、POSTやDELETEなど他のHTTP Methodも使うことになります。

API呼び出し時に渡したいパラメータは、クエリーパラメータもしくはapplication/x-www-form-urlencoded形式で渡します。その際、基本的に指定が必要となるパラメータがいくつかあります。

  • access_token - 先ほど取得したアクセストークン文字列です。
  • key - アプリ登録時に発行されたKey値。
  • site - APIの対象となる値。stackoverflowを指定します。

上記はAPIに関わらず指定されるパラメータです。それに対して、複数の結果を返却する可能性があるAPIでは、ページングがサポートされています。そのページングを制御するために、以下のパラメータが利用可能です。

  • page - 取得したいページの番号。最初のページは1です。
  • pagesize - 1ページあたりの件数です。未指定の場合は30が適用されます。0〜100の間の数値を指定可能です。

ページングに関してのより詳しい情報は、Pagingを参照してください。あと、ソート順を指定するoderや、取得対象を限定するminmaxfromdatetodateなどもあります。これらについては、Complex Queriesを参考にすると良いでしょう。

もう一つ大事なことがあります。StackOverflowが提供するAPIのレスポンスは、原則として圧縮がかかります。つまり、Accept-Encoding: gzipが常に適用されている状況になります(identityを指定しても、Accept-Encodingを省略しても、GZIP圧縮がかかる)。そのため、curlコマンドを使う場合は、パイプでgunzipコマンドに流してあげると、人間が読める表示を得ることができるようになります。

curl ... | gunzip -

提供されているAPI

StackOverflowで提供されているAPIの一覧を簡単に紹介します。

種類 パス
回答 /answers/...
バッジ /badges/...
コメント /comments/...
イベント /events
サイト情報 /info
投稿(質問と回答) /posts/...
栄誉 /privileges
質問 /questions/...
リビジョン /revisions/...
検索 /search/..., /similar
編集提案 /suggested-edits/...
タグ /tags/...
ユーザ /users/...

上記はStackOverflowのAPIですが、StackExchange全体で提供されているAPIがいくつかあります。下記のAPIは、siteパラメータを指定する必要はありません。

種類 パス
アクセストークン /access-tokens/...
アプリケーション /apps/.../de-authenticate
エラー /errors/...
フィルター /filters/...
インボックス /inbox/...
通知 /notifications/...
サイト /sites
ユーザ /users/.../associated, /users/.../merges

これらのAPIの詳細は、Stack Exchange API v2.2をご覧ください。

API呼び出しの例

いくつかAPIを実際に呼び出した結果を紹介したいと思います。

GET /me(認可ユーザの情報取得)

Request
curl https://api.stackexchange.com/2.2/me?access_token=[ACCESS_TOKEN]&site=stackoverflow&key=[KEY] | gunzip -
Response
{
    "quota_remaining": 9993,
    "quota_max": 10000,
    "has_more": false,
    "items": [
        {
            "display_name": "Yoichiro Tanaka",
            "profile_image": "https://i.stack.imgur.com/GsSj6.jpg?s=128&g=1",
            "link": "http://stackoverflow.com/users/1406101/yoichiro-tanaka",
            "website_url": "https://www.eisbahn.jp/yoichiro",
            "location": "\u65e5\u672c T\u014dky\u014d",
            "user_id": 1406101,
            "user_type": "registered",
            "creation_date": 1337509122,
            "reputation": 343,
            "reputation_change_day": 0,
            "reputation_change_week": 0,
            "reputation_change_month": 72,
            "reputation_change_quarter": 232,
            "reputation_change_year": 232,
            "last_access_date": 1457824686,
            "last_modified_date": 1456869085,
            "is_employee": false,
            "account_id": 1501656,
            "badge_counts": {
                "gold": 0,
                "silver": 1,
                "bronze": 6
            }
        }
    ]
}

GET /me/answers(認可ユーザの回答取得)

Request
curl https://api.stackexchange.com/2.2/me/answers?access_token=[ACCESS_TOKEN]&site=stackoverflow&key=[KEY] | gunzip -
Response
{
    "quota_remaining": 9991,
    "quota_max": 10000,
    "has_more": false,
    "items": [
        {
            "question_id": 35780106,
            "answer_id": 35786329,
            "creation_date": 1457054554,
            "last_edit_date": 1457067058,
            "last_activity_date": 1457067058,
            "score": 4,
            "is_accepted": true,
            "owner": {
                "link": "http://stackoverflow.com/users/1406101/yoichiro-tanaka",
                "display_name": "Yoichiro Tanaka",
                "profile_image": "https://i.stack.imgur.com/GsSj6.jpg?s=128&g=1",
                "user_type": "registered",
                "user_id": 1406101,
                "reputation": 343
            }
        },
        ...
    ]
}

GET /questions/{id}(質問の取得)

Request
curl https://api.stackexchange.com/2.2/questions/35780106?access_token=[ACCESS_TOKEN]&site=stackoverflow&key=[KEY] | gunzip -
Response
{
    "quota_remaining": 9990,
    "quota_max": 10000,
    "has_more": false,
    "items": [
        {
            "title": "Opening Tab Next to Active Tab",
            "link": "http://stackoverflow.com/questions/35780106/opening-tab-next-to-active-tab",
            "question_id": 35780106,
            "creation_date": 1457029701,
            "last_activity_date": 1457067058,
            "score": 0,
            "answer_count": 1,
            "accepted_answer_id": 35786329,
            "view_count": 51,
            "is_answered": true,
            "owner": {
                "link": "http://stackoverflow.com/users/6002362/john-sewell",
                "display_name": "John Sewell",
                "profile_image": "https://www.gravatar.com/avatar/bccb971ed949c1b334b7bba197b19708?s=128&d=identicon&r=PG&f=1",
                "user_type": "registered",
                "user_id": 6002362,
                "reputation": 45
            },
            "tags": [
                "google-chrome-extension"
            ]
        }
    ]
}

GET /answers/{id}(回答の取得)

Request
curl https://api.stackexchange.com/2.2/answers/35786329?access_token=[ACCESS_TOKEN]&site=stackoverflow&key=[KEY] | gunzip -
Response
{
    "quota_remaining": 9989,
    "quota_max": 10000,
    "has_more": false,
    "items": [
        {
            "question_id": 35780106,
            "answer_id": 35786329,
            "creation_date": 1457054554,
            "last_edit_date": 1457067058,
            "last_activity_date": 1457067058,
            "score": 4,
            "is_accepted": true,
            "owner": {
                "link": "http://stackoverflow.com/users/1406101/yoichiro-tanaka",
                "display_name": "Yoichiro Tanaka",
                "profile_image": "https://i.stack.imgur.com/GsSj6.jpg?s=128&g=1",
                "user_type": "registered",
                "user_id": 1406101,
                "reputation": 343
            }
        }
    ]
}