APIリファレンス
このページでは、qnyp GraphQL API(以下API)の仕様について解説します。
APIを利用すると、qnypに登録されている情報を取得したり、ユーザーとしてエピソードの感想を記録することができます。
APIの利用に関する質問や要望は qnyp/qnyp.dev の Issues までどうぞ。
1. Overview
APIはGraphQLをベースとしています。すべてのリクエストはエンドポイント https://api.qnyp.com/graphql
に対して送信します。
以下のGraphQLクライアントライブラリおよびクライアントソフトウェアでの動作を確認しています。
- graphql-ruby - Ruby
- Apollo Client - JavaScript
- GraphiQL.app - A light, Electron-based wrapper around GraphiQL
また、Ruby開発者向けに以下のライブラリおよびサンプルを公開しています。
- omniauth-qnyp - omniauth用ストラテジー
- qnyp-graphql-example-ruby - graphql-clientを使ったサンプル
2. Authorization
APIを利用するにはアクセストークンが必要となります。
アクセストークンは以下のいずれかの方法で取得します。
- パーソナルアクセストークンを利用する
- OAuth2アプリケーションをqnypに登録し、ユーザーからの許可を得てアクセストークンを取得する
それぞれの想定するユースケースは以下の通りです。
- パーソナルアクセストークン
- 自分用のツールを作成する場合
- GraphQLクライアントアプリケーションからAPIを呼び出す場合
- OAuth2アプリケーション
- qnypユーザー向けのWebアプリケーションを公開する場合
- qnypユーザー向けのモバイルアプリやデスクトップアプリを公開する場合
また、APIを試してみたい場合には、qnyp GraphQL Explorerを利用することでアクセストークンをあらかじめ準備することなく、APIの呼び出しやドキュメントの参照を行うことができます。
2.1. パーソナルアクセストークン
パーソナルアクセストークンとは、qnypのユーザーが自分で生成・破棄を行うことができるアクセストークンです。
パーソナルアクセストークンを生成するには、qnypにログインした状態で https://qnyp.com/settings/api へアクセスして、アクセストークンの生成操作を行います。
パーソナルアクセストークンに有効期限はありませんが、ユーザーはいつでも設定ページから自分のアクセストークンを無効化することができます。
2.2. OAuth2アプリケーション
OAuth2アプリケーションとしてユーザーの許可を得てアクセストークンを取得するには、まずqnypにログインしている状態で https://qnyp.com/oauth/applications にてアプリケーションの登録(作成)を行います。
アプリケーションの登録が完了すると「アプリケーションID」と「アプリケーションシークレット」が発行されるので、これを使ってOAuth2の認可フローを開始します。
2.2.1 認可フロー
ここでは、qnypのユーザーからアプリケーションに対する認可を得る手順について説明します。
なお、qnypではOAunt 2.0で定義されている認可フローのうち「認可コードフロー(Authorization Code Flow)」のみをサポートしていますので、以降で説明する手順はそれに沿ったものとなります。
qnypへのリダイレクト:
ユーザーに認可を求めるには、まずあなたのアプリケーションから次ようなURLへのリダイレクトを行います。
GET https://qnyp.com/oauth/authorize?client_id={アプリケーションID}&response_type=code&scope={スコープ}&redirect_uri={コールバックURL}&state={anti-forgery-token}
client_id
には登録したアプリケーションの「アプリケーションID」を指定します。scope
にはユーザーに求める権限をスペース区切りおよびURLエンコードした文字列で指定します(例:public%20write
)。スコープの詳細については「3. スコープ」を参照してください。redirect_uri
には認可後にユーザーが戻ってくるあなたのアプリケーションのURLを指定します。このURLは、登録したアプリケーションのコールバックURLに含まれている必要があります。state
に指定された値は、そのままコールバックURLへのリダイレクト時に持ち越されてきます。この値はCSRFを防ぐために使います。
qnypからのリダイレクト:
ユーザーがqnyp上で認可の要求を「認証」または「否認」すると、以下のようなURLにユーザーがリダイレクトされてきます。
GET {コールバックURL}?code={コード}&state={anti-forgery-token}
アクセストークンの取得:
$ curl -X POST https://api.qnyp.com/oauth/token \
-d "client_id={アプリケーションID}" \
-d "client_secret={アプリケーションシークレット}" \
-d "code={コード}" \
-d "grant_type=authorization_code" \
-d "redirect_uri={コールバックURL}"
{
"access_token": "bbfe3562bcf500fafac64397858ee9a8c1676959c9499726a4078218ec1546c9",
"token_type": "bearer",
"scope": "public write",
"created_at": 1493395116
}
リダイレクトによって受け取ったcode
の値をはじめとする以下のパラメーターをAPIの /oauth/token
にPOSTリクエストで送信すると、アクセストークンを取得することができます。
名前 | 値 |
---|---|
client_id |
登録したアプリケーションのアプリケーションID |
client_secret |
登録したアプリケーションのアプリケーションシークレット |
code |
qnypからのリダイレクトで受け取ったコード |
grant_type |
authorization_code |
redirect_uri |
登録したアプリケーションのコールバックURL |
レスポンスは以下の値を含むJSONとなります。
名前 | 値 |
---|---|
acces_token |
アクセストークン |
token_type |
bearer |
scope |
アクセストークンが持つスコープ(半角スペース区切り) |
created_at |
アクセストークンの生成日時(UNIXタイムスタンプ) |
発行されたアクセストークンに有効期限はありません。
2.3. アクセストークンによる認証
アクセストークンを使ってAPIへのリクエストを行うには、Authorization
ヘッダに Bearer
タイプとしてアクセストークンを指定します。
Authorization: Bearer アクセストークン
3. Scopes
アクセストークンの持つスコープによって、利用できるAPIが異なります。
API | 必要なスコープ |
---|---|
GraphQLのQuery | public |
GraphQLのMutation | public および write |
公開情報の参照のみでよい場合は public
スコープを、視聴ログの作成などの書き込み操作を行う場合は public
と write
スコープの両方を要求してください。
4. Errors
APIは、エラーが発生した際に以下のステータスコードを返します。
コード | 意味 |
---|---|
401 | アクセストークンが無効である |
403 | アクセストークンがAPIリクエストに必要な権限を持っていない |
404 | 無効なエンドポイントをリクエストした |
429 | リクエスト数が上限に達している |
500 | サーバー側でエラーが発生した |
503 | APIがメンテナンス中である |
また、GraphQLリクエストに含まれるQueryおよびMutationの実行中に発生するエラー(バリデーションエラーなど)の場合のステータスコードは200となり、エラーの詳細がレスポンスボディに含まれます。
5. Rate limiting
# 通常時のレスポンス
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
X-RateLimit-Reset: Tue, 14 Mar 2017 16:16:00 GMT
# リクエスト数が上限に達した場合のレスポンス
429 Too Many Requests
Retry-After: 3600
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 0
X-RateLimit-Reset: Tue, 14 Mar 2017 16:16:00 GMT
APIへのリクエスト数の上限は、アクセストークン毎に1時間あたり500回までとなっています。
リクエスト数の残数や回数がリセットされる日時などの情報は、APIのレスポンスヘッダに以下のような形で含まれています。
ヘッダ | 値 |
---|---|
X-RateLimit-Limit |
リクエスト数の上限 |
X-RateLimit-Remaining |
リクエスト数の残り回数 |
X-RateLimit-Reset |
リクエスト数がリセットされる日時 |
リクエスト数が上限に達した場合は、ステータスコード429のレスポンスが返されます。また、その際にリクエスト数がリセットされるまでの秒数が Retry-After
レスポンスヘッダで返されます。
6. GraphQL
6.1. Objects
GraphQL APIではqnyp上の主要なデータが以下のようなオブジェクトとして表現されます。
型 | 意味 |
---|---|
Title |
タイトル。 TVシリーズやOVAなどのアニメ作品。 |
Episode |
エピソード。 タイトルに属する1つのエピソード。 |
User |
ユーザー。 qnypのユーザーアカウント。 |
Log |
視聴ログ。 ユーザーがエピソードに対して記録した感想。 |
各オブジェクトの関係はおおまかに以下のようになります。
Title
は0個以上のEpisode
を持つEpisode
は0個以上のLog
を持つUser
は0個以上のLog
を持つ
各オブジェクトの詳細な仕様に関しては GraphQL Explorer の Docs および GraphQL Schema document を参照してください。
6.2. ID
GraphQL APIにおける各オブジェクトは識別子として id (String)
と databaseId (Int)
の2種類のIDを持ちます。
属性 | 説明 |
---|---|
id |
すべてのオブジェクトにおいて一意な文字列 例: "VGl0bGUtMzUwMQ" |
databaseId |
データベースにおけるプライマリキー 同一のオブジェクトにおいて一意な数値 例: 3501 |
GraphQL APIの操作においては基本的に id
を使います。databaseId
は「既にqnypのサイト上でIDがわかっているオブジェクトを取得する」場合の利便性のために用意されています。
6.2. Queries
情報を取得する場合は、 Query
ルートタイプが持つ以下のフィールドに対するクエリをAPIに送ります。
フィールド | 機能 |
---|---|
episode |
idでエピソードを取得 |
episodeByDatabaseId |
databaseIdでエピソードを取得 |
log |
idで視聴ログを取得 |
logByDatabaseId |
databaseIdで視聴ログを取得 |
node |
idでオブジェクトを取得 |
nodes |
idでオブジェクトを一括取得 |
searchTitles |
タイトルを検索 |
title |
idでタイトルを取得 |
titleByDatabaseId |
databaseIdでタイトルを取得 |
user |
ユーザー名でユーザーを取得 |
viewer |
認証したユーザーを取得 |
6.2.1 Query の例
query {
viewer {
id
databaseId
username
profileImageURL
recentlyWatchedTitles(first: 1) {
edges {
node {
name
}
}
}
logs(first: 1) {
edges {
node {
id
channel
rating
episode {
numberText
title {
name
}
}
}
}
}
}
}
{
"data": {
"viewer": {
"id": "VXNlci0x",
"databaseId": 1,
"username": "junya",
"profileImageURL": "//qnyp.global.ssl.fastly.net/attachments/05da1bf0b79769073ad0a2543f411521da298fa0/store/fill/80/80/1a63fa09379efe7e52335cfa74ecda8106585f6cb4ca6a91641f8ef6df0c/profile_image.png",
"recentlyWatchedTitles": {
"edges": [
{
"node": {
"name": "月刊少女野崎くん"
}
}
]
},
"logs": {
"edges": [
{
"node": {
"id": "TG9nLTIxNDk2",
"channel": "NET",
"rating": "GREAT",
"episode": {
"numberText": "最終号",
"title": {
"name": "月刊少女野崎くん"
}
}
}
}
]
}
}
}
}
viewer
フィールドから、「アクセストークンで認証したユーザー」の
- ユーザー情報
- 最近見たタイトル1件の情報
- 最近作成した視聴ログ1件の情報
を取得する例です。
6.2.2. Connection
一部のオブジェクトは Connection
で終わる型名のフィールドを持っています(例えば User
オブジェクトの logs
フィールドは LogConnection
型)。
これらのコネクション表現はオブジェクト間の1対多関係を表現しており、カーソルを使ったページングをシンプルに実現するためのものとなっています。
その実装は Relay Cursor Connections Specification に沿ったものとなっていますので、詳細についてはそれらのドキュメントを参照してください。
6.3. Mutations
情報を更新する場合は、 Mutation
ルートタイプが持つ以下のmutationフィールドに対するクエリをAPIに送ります。
フィールド | 機能 |
---|---|
createLog |
視聴ログを作成する |
deleteLog |
視聴ログを削除する |
updateLog |
視聴ログを更新する |
Mutation を実行するには、アクセストークンに public
および write
スコープが与えられている必要があります。
6.3.1 Mutation の例
mutation ($input: CreateLogInput!) {
createLog(input: $input) {
log {
id
databaseId
createdAt
url
}
}
}
// Query Variables
{
"input": {
"body": "楽しめるエピソードでした。",
"channel": "TV",
"episodeId": "RXBpc29kZS03NTY1OQ",
"rating": "GREAT",
"spoiler": false
}
}
// Response
{
"data": {
"createLog": {
"log": {
"id": "TG9nLTIxMjE3",
"databaseId": 99999,
"createdAt": "2017-04-29T11:17:22Z",
"url": "https://qnyp.com/junya/logs/99999"
}
}
}
}
createLog
フィールドで、エピソードに対する視聴ログを作成し、作成された視聴ログの情報を取得する例です。