REST APIの使用方法

Feedeenのデータにアクセスできる REST 形式の API を試験的に公開しています。現在は試験公開ということで、自分自身のデータにのみアクセスでき、取得できるデータも限定されています。もし必要な API がない場合は、使用目的などをお問合せフォームよりお知らせください。皆様のご意見を伺いつつ、 API を拡張していこうと考えております。

利用のための準備

REST API を利用するためには、開発者登録と認証情報の取得が必要です。以下で準備のための手順をご説明します。

開発者登録

API を利用したい方は、 Feedeen にログインした状態でお問合せフォームからその旨をご連絡ください。当方で必要な設定を行い、折り返しご連絡させていただきます。アカウント等にご連絡いただいても、 Feedeen アカウントの特定ができず設定が行なえませんので、必ずログインした状態でお問合せフォームをご利用ください。

単に設定作業の都合でご連絡をお願いしているだけで、とくに審査などを行うことはありません。 Feedeen ユーザーであればどなたでも登録する方針ですので、お気軽にご連絡ください。

認証情報の確認

開発者登録が終了したら、詳細設定の「アカウント」タブを開いてください。「開発者情報を表示する」というボタンがあり、それをクリックすると「クライアントID」と「シークレット」が表示されます。

アカウント画面

認証情報が表示されたところ

これらの情報は API のアクセストークンを取得する際に必要となります。

API へのアクセス方法

Feedeen の REST API は OAuth 2.0 の JWT Profile に準拠しており、Google の一連の API などと同じ手順でアクセスできます(ただし、JWTの署名方法は HS256 のみです)。ここでは、 API にアクセスする手順を簡単にご説明します。ページの最後に Ruby と Python のサンプルコードも掲載しているので、あわせて参考にしてください。

アクセストークンの取得

API にアクセスする前に、認証情報を格納した JWT (JSON Web Token) を既定の URL に POST して、アクセストークンを取得する必要があります。送信する JWT には以下の情報を格納してください。

フィールド名 格納する値
iss アカウント画面に表示されるクライアントID
aud 常に "http://home.sourcewalker.com:3000/api/" を指定
exp このトークンが期限切れするエポック秒(整数)

この情報を、アカウント画面に表示されるシークレットで HS256 署名して JWT を生成します。 Ruby で jwt gem を使用する場合、以下のコードになります。

JWTの生成

require 'jwt'

payload = {
  'iss' => 'クライアントID',
  'aud' => 'https://www.feedeen.com/api/auth',
  'exp' => Time.now.to_i + 60
}
jwt = JWT.encode(payload, 'シークレット', 'HS256')

生成した JWT を、以下のフィールドを持つ x-www-form-urlencoded  形式で https://www.feedeen.com/api/auth に POST してください。

フィールド名 格納する値
grant_type常に "urn:ietf:params:oauth:grant-type:jwt-bearer" を指定
assertion生成した JWT

レスポンスとして以下の JSON データが返ります。

レスポンス

{
  "token_type": "Bearer",
  "access_token": "...",
  "expires_in": 3600
}

access_token フィールドの値がアクセストークンとなります。 expires_in の秒数(現在のところ1時間に固定)経過するとトークンが無効化されますので、その場合は同じ方法で新しいトークンを取得してください。

APIのアクセス

各 API にアクセスするときは、以下のリクエストヘッダを指定してください。「アクセストークン」の部分は上で取得したアクセストークンに置き換えてください。アクセストークンはヘッダに使える文字しか含まないので、エスケープ等は不要です。

リクエストヘッダ

Authorization: Bearer アクセストークン

注意事項

Feedeen のサーバーはお世辞にも強力とは言えないので、大量のリクエストを送信するのは避けてください。とくに、以下の点にご配慮いただけると助かります。
  • 現在のAPIで取得できる情報は頻繁に変化するものではないので、短い間隔でのポーリングは避けてください。公式のブラウザ拡張も、未読数の取得頻度は5〜10分に一回程度です。
  • アクセストークンはできるだけ使い回してください。API呼び出しのたびにアクセストークンを取得することはせず、期限切れが近づくまでは同じトークンを再利用してください。

利用できるAPI

現在利用できるAPIは、購読リストと未読数の取得のみです。必要に応じて拡張していこうと考えておりますので、必要な機能があれば、使用目的などをお問合せフォームよりお知らせください。実装を検討させていただきます。

以下、それぞれのAPIの仕様です。

GET /api/feeds

自分が購読しているフィードのリストを返します。

HTTPメソッド
GET

URL
https://www.feedeen.com/api/feeds

クエリーパラメータ
なし

レスポンスボディ
以下の形式のJSONデータが返ります。

レスポンスボディ

{
  "data": [
    {
      "id": 1,         // フィードID(一意な数値)
      "name": "...",   // フィード名
      "url": "...",    // フィードのURL
      "site": "...",   // 発行元のWebページのURL
      "favicon": "..." // ファビコンのURL
    }, ...
  ]
}

GET /api/items/count

各フィードの未読数を返します。

HTTPメソッド
GET

URL
https://www.feedeen.com/api/items/count

クエリーパラメータ
mode: default, all, total のいずれか。省略時は default

リクエストボディ
mode の値によって以下のJSONデータが返ります。

mode=default(または省略)のレスポンスボディ

{
  "data": {
    "フィード1のID": フィード1の未読数,
    "フィード2のID": フィード2の未読数,
    ...
    "unliked": フィルタされたアイテムの未読数
  }
}
※ 「"フィード1のID"」等は /api/feeds で返るフィードIDを文字列化したもの。

mode=all のレスポンスボディ

{
  "data": {
    "フィード1のID": フィード1の未読数,
    "フィード2のID": フィード2の未読数,
    ...
    "unliked": フィルタされたアイテムの未読数,
    "messages": お知らせの未読数
  }
}

mode=total のレスポンスボディ

{
  "total": 未読数の合計
}
※ 「未読数の合計」はアプリ上の「すべてのアイテム」の未読数と同じです。フィルタされたアイテムやお知らせの未読数は含まれません。

サンプルコード

参考として、 Ruby, Python で各APIにアクセスするサンプルを掲載します。コードを短くするため、エラー処理やセキュリティ対策等は最低限となっているのでご注意ください。

Ruby

Rubyで各APIにアクセスするサンプルです。 jwt gem を使用しているので、 gem install jwt であらかじめインストールしてください。

Ruby のサンプルコード

require 'net/http'
require 'pp'
require 'uri'
require 'jwt'

CLIENT_ID  = '...' # 正しいクライアントIDに置き換えてください
SECRET     = '...' # 正しいシークレットに置き換えてください
ENDPOINT   = 'https://www.feedeen.com/api/'
AUD        = 'https://www.feedeen.com/api/auth'
GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'

def auth
  jwt = JWT.encode({ iss: CLIENT_ID, aud: AUD, exp: Time.now.to_i + 60 }, SECRET, 'HS256')
  rsp = Net::HTTP.post_form(URI.parse(ENDPOINT + 'auth'),
                            { 'assertion' => jwt, 'grant_type' => GRANT_TYPE })
  JSON.load(rsp.value || rsp.body)['access_token']
end

def send_request(method, path, token)
  uri = URI.parse(ENDPOINT + path)
  headers = { 'Authorization' => "Bearer #{token}" }
  puts("#{method.upcase} #{uri}")
  http = Net::HTTP.new(uri.host, uri.port || (uri.scheme == 'http' ? 80 : 443))
  http.use_ssl = (uri.scheme == 'https')
  http.start do
    rsp  = http.send_request(method.upcase, uri.request_uri, nil, headers)
    JSON.load(rsp.value || rsp.body)
  end
end

def main
  token = auth
  pp send_request('GET', 'feeds', token)
  pp send_request('GET', 'items/count', token)
end

main

Python

Pythonで各APIにアクセスするサンプルです。 PyJWT を使用しているので、 pip3 install PyJWT であらかじめインストールしてください。

Python のサンプルコード

import json, jwt, time
from pprint import pprint
from urllib.parse import urlencode
from urllib.request import Request, urlopen

CLIENT_ID  = '...' # 正しいクライアントIDに置き換えてください
SECRET     = '...' # 正しいシークレットに置き換えてください
ENDPOINT   = 'https://www.feedeen.com/api/'
AUD        = 'https://www.feedeen.com/api/auth'
GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'

def auth():
    token = jwt.encode(dict(iss=CLIENT_ID, aud=AUD, exp=int(time.time()) + 60), SECRET, 'HS256')
    data = urlencode([('assertion', token.decode('ascii')), ('grant_type', GRANT_TYPE)])
    rsp = urlopen(ENDPOINT + 'auth', data.encode('ascii'))
    if rsp.status != 200:
        raise RuntimeError('Request error: %s' % rsp.read().decode('UTF-8', 'replace'))
    return json.loads(rsp.read().decode('UTF-8'))['access_token']

def send_request(method, path, token):
    hdr = { 'Authorization': 'Bearer ' + token }
    req = Request(ENDPOINT + path, headers=hdr, method=method.upper())
    rsp = urlopen(req)
    if rsp.status != 200:
        raise RuntimeError('Request error: %s' % rsp.read().decode('UTF-8', 'replace'))
    return json.loads(rsp.read().decode('UTF-8', 'replace'))

def main():
    token = auth()
    pprint(send_request('GET', 'feeds', token))
    pprint(send_request('GET', 'items/count', token))


if __name__ == '__main__':
    main()

Clojure

@anekos 様により、 Clojure によるクライアントの実装が公開されています。ありがとうございます!

anekos/feedeen-stat

他にもサンプル等を実装された方がいらっしゃいましたら、ぜひご連絡ください。このページからリンクさせていただきます。

Comments