± Revisions rev80

Atsuo Fukaya
Change log: Add X-RateLimit-Reset header
Updated by fukayatsu 2017-04-07 15:16:35 +0900
  • # はじめに
  • この記事では esa API v1について説明します。
  • # 概要
  • ## リクエスト
  • APIとの通信には `HTTPS` プロトコルを使用します。アクセス先のホストは `api.esa.io` です。
  • ## パラメータ
  • API v1へのリクエストには、 `GET` / `POST` / `PUT` / `PATCH` / `DELETE` の HTTPメソッドを利用します。
  • 多くのAPIはリクエスト時にパラメータを含められますが、GETリクエストにはURIクエリ文字列を利用し、それ以外の場合にはリクエストボディを利用します。パラメータにはページネーション等の任意で渡すものと、記事名等の必須のものが存在します。
  • ## 利用制限
  • 現時点では、ユーザ毎に15分間に75リクエストまで受け付けます。
  • 制限状況はAPIのレスポンスヘッダに含まれる `X-RateLimit-*` の値をご確認下さい。
  • ```
  • X-RateLimit-Limit: 75
  • X-RateLimit-Remaining: 73
  • X-RateLimit-Reset: 1491543000
  • ```
  • なお、決められた制限を超える場合は、`429 Too Many Requests` が返却されます。
  • ## ステータスコード
  • `200` / `201` / `204` / `400` / `401` / `402` / `403` / `404` / `500` のステータスコードを利用します。
  • ## データ形式
  • APIとのデータの送受信にはJSONを利用します。JSONをリクエストボディに含める場合は`Content-Type: application/json`をリクエストヘッダに追加して下さい。文字コードはUTF-8を使用して下さい。
  • ## エラーレスポンス
  • エラーが発生した場合は、エラーを表現するオブジェクトを含んだエラーレスポンスが返却されます。
  • ```json
  • {
  • "error": "not_found",
  • "message": "Not found"
  • }
  • ```
  • ## ページネーション
  • リストを返すAPIでは、すべての要素を一度に返すことは現実的ではないため、ページを指定することができるようになっています。これらのAPIには、1から始まるページ番号を表す `page` パラメータと、1ページあたりに含まれる要素数を表す `per_page` パラメータを指定することができます。`page` の初期値は `1` に設定されています。また、 `per_page` の初期値は `20`、最大値は `100` に設定されています。
  • ページを指定できるAPIでは、ページネーションの情報を含んだレスポンスを返します。
  • ```
  • # GET /v1/teams HTTP/1.1
  • {
  • "teams": [
  • ...
  • ],
  • "prev_page": null,
  • "next_page": 2,
  • "total_count": 30,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • - prev_page
  • - 1つ前のpage番号。存在しない場合はnull
  • - next_page
  • - 1つ先のpage番号。存在しない場合はnull
  • - total_count
  • - リソースの総数
  • - page
  • - 現在のページ番号
  • - per_page
  • - 1ページあたりに含まれる要素数
  • - max_per_page
  • - per_pageに指定可能な数の最大値
  • ## クライアントライブラリ
  • 現時点で以下のライブラリが利用可能です。
  • - Ruby
  • - [esaio/esa-ruby](https://github.com/esaio/esa-ruby)
  • # 認証と認可
  • esa APIを利用するには、アクセストークンをリクエストに含める必要があります。アクセストークンは、ユーザの管理画面(https://[team].esa.io/user/tokens)もしくはOAuthを利用した認可フローにより発行できます。
  • ## アクセストークン
  • アクセストークンは、以下のようにリクエストヘッダに含められます。
  • ```
  • Authorization: Bearer 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
  • ```
  • また、URIクエリ文字列として指定することも可能です。
  • ```
  • api.esa.io/v1/teams?access_token=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
  • ```
  • ## スコープ
  • 個々のアクセストークンには、複数のスコープを紐付けることができます。アクセストークンが適切なスコープを持っている時のみAPIを利用することができます。esa APIでは以下のスコープを利用することができます。
  • | スコープ | 説明 |
  • | --- | --- |
  • | read | データの読み出し |
  • | write | データの書き込み |
  • ## OAuthを利用した認可フロー
  • ### アプリケーションの登録
  • `https://[team].esa.io/user/applications` から アプリケーションを登録できます。
  • ※ RailsやSinatraでは、以下の手順は [omniauth-esa](https://github.com/esaio/omniauth-esa) を使うことで実装の手間を省くことが出来ます。
  • ### GET /oauth/authorize
  • アプリケーションのユーザーに認可画面を表示します。
  • - client_id
  • - 登録されたアプリケーションのclient_idです
  • - redirect_uri
  • - 認可が行われた後のリダイレクト先を指定します。アプリケーション登録時に設定したredirect_uriのうちいずれかに一致する必要があります。
  • - `urn:ietf:wg:oauth:2.0:oob`を指定した場合、認可後にリダイレクトせず画面に認証コードを表示します
  • - scope
  • - アプリケーションが利用するスコープをスペース区切りで指定します
  • - response_type
  • - `code` を指定します
  • - state
  • - CSRF対策のため、認可後にリダイレクトするURLのクエリに含まれる値を指定できます
  • ```
  • GET /oauth/authorize?client_id=0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=read+write&state=a7e567e2fb858f0e12838798016ee9cf8ccc778 HTTP/1.1
  • ```
  • <img width="578" alt="ss 2016-04-21 14.49.33.png (138.0 kB)" src="https://img.esa.io/uploads/production/attachments/105/2016/04/21/1/781dc446-c038-49aa-81f0-7486d9a4e265.png">
  • ### POST /oauth/token
  • 新規にアクセストークンを発行します。
  • - client_id
  • - 登録されたアプリケーションのClient IDです
  • - client_secret
  • - 登録されたアプリケーションのClient Secretです
  • - grant_type
  • - `authorization_code ` を指定します
  • - redirect_uri
  • - 認可を行った際のredirect_uriを指定します
  • - code
  • - 認可後のリダイレクト先のURLに含まれた認可コードです
  • ```
  • POST /oauth/token HTTP/1.1
  • {"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","code":"c486e26492dfdc42bcf4e07bfbd84a97f98548c1cf89178a512e76b79b099313","grant_type":"authorization_code","redirect_uri":"urn:ietf:wg:oauth:2.0:oob"}
  • ```
  • ```
  • HTTP/1.1 200 OK
  • {
  • "access_token": "5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc",
  • "token_type": "bearer",
  • "scope": "read write",
  • "created_at": 1461218696
  • }
  • ```
  • ### GET /oauth/token/info
  • アクセストークンの情報を取得します
  • ```
  • GET /oauth/token/info HTTP/1.1
  • Authorization: bearer 5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc
  • ```
  • ```
  • HTTP/1.1 200 OK
  • {"resource_owner_id":1,"scopes":["read","write"],"expires_in_seconds":null,"application":{"uid":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04"},"created_at":1461218696}
  • ```
  • ### POST /oauth/revoke
  • アクセストークンを失効させます
  • - client_id
  • - 登録されたアプリケーションのClient IDです
  • - client_secret
  • - 登録されたアプリケーションのClient Secretです
  • - token
  • - アクセストークンを指定します
  • ```
  • POST /oauth/revoke HTTP/1.1
  • {"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","token":"5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc"}
  • ```
  • ```
  • HTTP/1.1 200 OK
  • {}
  • ```
  • # チーム
  • esa上で所属しているチームを表します。
  • - name (String)
  • - チームを特定するための一意なIDです。サブドメインになります。
  • - Example: "docs"
  • - privacy (String)
  • - チームの公開範囲です。
  • - closed: "チームメンバーだけが情報にアクセスできます。"
  • - open: "ShipItされた記事はインターネット上に公開されます。"
  • - description (String)
  • - チームの説明です
  • - Example: "esa.io official documents"
  • - 登録がない場合には空文字列(`""`)になります
  • - icon (String)
  • - チームのアイコンです
  • - Example: "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png"
  • MEMO: member_count等の情報を追加予定
  • ## GET /v1/teams
  • ```
  • GET /v1/teams HTTP/1.1
  • ```
  • ```
  • {
  • "teams": [
  • {
  • "name": "docs",
  • "privacy": "open",
  • "description": "esa.io official documents",
  • "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
  • "url": "https://docs.esa.io/"
  • }
  • ],
  • "prev_page": null,
  • "next_page": null,
  • "total_count": 1,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • ## GET /v1/teams/:team_name
  • ```
  • GET /v1/teams/docs HTTP/1.1
  • ```
  • ```
  • {
  • "name": "docs",
  • "privacy": "open",
  • "description": "esa.io official documents",
  • "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
  • "url": "https://docs.esa.io/"
  • }
  • ```
  • # 統計情報
  • チームの統計情報を表します。
  • - members(Integer)
  • - チーム内のメンバーの総数です
  • - posts(Integer)
  • - チーム内の記事の総数です
  • - posts_wip(Integer)
  • - チーム内の記事(wip)の総数です
  • - posts_shipped(Integer)
  • - チーム内の記事(shipped)の総数です
  • - comments(Integer)
  • - チーム内の記事につけられたコメントの総数です
  • - stars(Integer)
  • - チーム内の記事につけられたスターの総数です
  • - daily_active_users(Integer)
  • - 過去24時間以内に記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
  • - weekly_active_users(Integer)
  • - 過去7日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
  • - monthly_active_users(Integer)
  • - 過去30日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
  • ## GET /v1/teams/:team_name/stats
  • ```
  • GET /v1/teams/foo/stats HTTP/1.1
  • ```
  • ```
  • {
  • "members": 20,
  • "posts": 1959,
  • "posts_wip": 59,
  • "posts_shipped": 1900,
  • "comments": 2695,
  • "stars": 3115,
  • "daily_active_users": 8,
  • "weekly_active_users": 14,
  • "monthly_active_users": 15
  • }
  • ```
  • # メンバー
  • チームのメンバーを表します。
  • - name (String)
  • - メンバーの名前です
  • - screen_name (String)
  • - メンバーのスクリーンネームです
  • - icon (String)
  • - メンバーのアイコンのURLです
  • - email (String)
  • - メンバーのemailです
  • - このフィールドは **team の owner** だけが取得可能です
  • - posts_count (Integer)
  • - チーム内でメンバーが作成した記事数です
  • ## GET /v1/teams/:team_name/members
  • ```
  • GET /v1/teams/foo/members HTTP/1.1
  • ```
  • ```
  • {
  • "members": [
  • {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
  • "email": "fukayatsu@esa.io",
  • "posts_count": 222
  • },
  • {
  • "name": "TAEKO AKATSUKA",
  • "screen_name": "taea",
  • "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg",
  • "email": "taea@esa.io",
  • "posts_count": 111
  • }
  • ],
  • "prev_page": null,
  • "next_page": null,
  • "total_count": 2,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • # 記事
  • ユーザが作成した記事を表します。
  • - number (Integer)
  • - チーム内で記事を特定するためのIDです
  • - Example: 123
  • - name (String)
  • - 記事名です。タグやカテゴリー部分は含みません。
  • - Example: "hi!"
  • - tags (Array of String)
  • - 記事に紐付けられたタグです。
  • - Example: ["api", "dev"]
  • - category (String)
  • - 記事が属するカテゴリです
  • - Example: "日報/2015/05/09"
  • - 設定されていない場合は `null` になります
  • - full_name (String)
  • - カテゴリとタグを含む、記事名です。
  • - Example: "日報/2015/05/09/hi! #api #dev"
  • - wip (Boolean)
  • - 記事がWIP(Working In Progress)状態かどうかを表します。
  • - Example: "true"
  • - body_md (String)
  • - Markdownで書かれた記事の本文です
  • - Example "# Getting Started"
  • - body_html (String)
  • - HTMLに変換された記事の本文です。
  • - Example: `<h1>Getting Started</h1>`
  • - created_at (DateTime String)
  • - 記事が作成された日時です
  • - Example: "2014-05-10T12:08:55+09:00"
  • - updated_at (DateTime String)
  • - 記事が更新された日時です。
  • - Example: "2014-05-11T19:21:00+09:00"
  • - message (String)
  • - 記事更新時の変更メモです。
  • - Example: "Add Getting Started section"
  • - revision_number(Integer)
  • - 記事のリビジョン番号です。
  • - Example: 47
  • - created_by (Object)
  • - 記事を作成したユーザを表します。
  • - name (String)
  • - ユーザ名です。
  • - Example: "Atsuo Fukaya"
  • - screen_name (String)
  • - ユーザを一意に識別するIDです。
  • - Example: "fukayatsu"
  • - icon (String)
  • - ユーザのアイコンのURLです。
  • - Exmaple: "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • - updated_by (Object)
  • - 記事を最後に更新したユーザを表します。フィールドは `created_by` と共通です。
  • - name (String)
  • - screen_name (String)
  • - icon (String)
  • - kind(String, "stock" or "flow")
  • - 記事の種別を表します
  • - comments_count(Integer)
  • - 記事へのコメント数を表します
  • - Example: 1
  • - tasks_count(Integer)
  • - 記事中のタスクの総数を表します
  • - Example: 1
  • - done_tasks_count(Integer)
  • - 記事中の完了済みのタスクの総数を表します
  • - Example: 1
  • - stargazers_count(Integer)
  • - 記事にStarをしている人数を表します
  • - Example: 1
  • - watchers_count(Integer)
  • - 記事をWatchしている人数を表します
  • - Example: 1
  • - star(Boolean)
  • - ユーザーが記事をStarしているかどうかを表します
  • - Example: true
  • - watch(Boolean)
  • - ユーザーが記事をWatchしているかどうかを表します
  • - Example: true
  • ## GET /v1/teams/:team_name/posts
  • 記事の一覧を更新日の降順で返却します
  • ```
  • GET /v1/teams/docs/posts HTTP/1.1
  • ```
  • ### URIクエリ文字列
  • - q (String)
  • - 記事を絞り込むための条件を指定します
  • - [#104: help/記事の検索方法](/posts/104) を参照
  • - include (String)
  • - `comments` を指定するとコメントの配列を含んだレスポンスを返します。
  • - `stargazers` を指定するとStarの配列を含んだレスポンスを返します。
  • - `stargazers,comments` のように `,` で区切ることで複数指定できます
  • - sort (String)
  • - 設定可能な値
  • - `updated` (default)
  • - 記事の更新日時
  • - `created`
  • - 記事の作成日時
  • - `stars`
  • - 記事へのStarの数
  • - `watches`
  • - 記事へのWatchの数
  • - `comments`
  • - 記事へのCommentの数
  • - `best_match`
  • - 総合的な記事のスコア
  • - order (String)
  • - 設定可能な値
  • - `desc` (default)
  • - 降順
  • - `asc`
  • - 昇順
  • ```
  • {
  • "posts": [
  • {
  • "number": 1,
  • "name": "hi!",
  • "full_name": "日報/2015/05/09/hi! #api #dev",
  • "wip": true,
  • "body_md": "# Getting Started",
  • "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  • "created_at": "2015-05-09T11:54:50+09:00",
  • "message": "Add Getting Started section",
  • "url": "https://docs.esa.io/posts/1",
  • "updated_at": "2015-05-09T11:54:51+09:00",
  • "tags": [
  • "api",
  • "dev"
  • ],
  • "category": "日報/2015/05/09",
  • "revision_number": 1,
  • "created_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "updated_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • }
  • }
  • ],
  • "prev_page": null,
  • "next_page": null,
  • "total_count": 1,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • ## GET /v1/teams/:team_name/posts/:post_number
  • 指定された投稿を取得します。
  • ### URIクエリ文字列
  • - include (String)
  • - `comments` を指定するとコメントの配列を含んだレスポンスを返します。
  • ```
  • GET /v1/teams/docs/posts/1 HTTP/1.1
  • ```
  • ```
  • {
  • "number": 1,
  • "name": "hi!",
  • "full_name": "日報/2015/05/09/hi! #api #dev",
  • "wip": true,
  • "body_md": "# Getting Started",
  • "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  • "created_at": "2015-05-09T11:54:50+09:00",
  • "message": "Add Getting Started section",
  • "url": "https://docs.esa.io/posts/1",
  • "updated_at": "2015-05-09T11:54:51+09:00",
  • "tags": [
  • "api",
  • "dev"
  • ],
  • "category": "日報/2015/05/09",
  • "revision_number": 1,
  • "created_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "updated_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "kind": "flow",
  • "comments_count": 1,
  • "tasks_count": 1,
  • "done_tasks_count": 1,
  • "stargazers_count": 1,
  • "watchers_count": 1,
  • "star": true,
  • "watch": true
  • }
  • ```
  • ## POST /v1/teams/:team_name/posts
  • 記事を新たに投稿します。
  • - post (Object)
  • - name (String, **required**)
  • - body_md (String)
  • - tags (Array of String)
  • - category (String)
  • - wip (Boolean, default: true)
  • - message (String)
  • - user(String)
  • - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
  • - このパラメータは **team の owner** だけ が使用することができます。
  • ```
  • POST /v1/teams/docs/posts HTTP/1.1
  • Content-Type: application/json
  • {
  • "post":{
  • "name":"hi!",
  • "body_md":"# Getting Started\n",
  • "tags":[
  • "api",
  • "dev"
  • ],
  • "category":"dev/2015/05/10",
  • "wip":false,
  • "message":"Add Getting Started section"
  • }
  • }
  • ```
  • ```
  • HTTP/1.1 201 Created
  • Content-Type: application/json
  • {
  • "number": 5,
  • "name": "hi!",
  • "full_name": "dev/2015/05/10/hi! #api #dev",
  • "wip": false,
  • "body_md": "# Getting Started\n",
  • "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  • "created_at": "2015-05-09T12:12:37+09:00",
  • "message": "Add Getting Started section",
  • "url": "https://docs.esa.io/posts/5",
  • "updated_at": "2015-05-09T12:12:37+09:00",
  • "tags": [
  • "api",
  • "dev"
  • ],
  • "category": "dev/2015/05/10",
  • "revision_number": 1,
  • "created_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "updated_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "kind": "flow",
  • "comments_count": 0,
  • "tasks_count": 0,
  • "done_tasks_count": 0,
  • "stargazers_count": 0,
  • "watchers_count": 1,
  • "star": false,
  • "watch": false
  • }
  • ```
  • ## PATCH /v1/teams/:team_name/posts/:post_number
  • 指定された投稿を編集します。
  • - post (Object)
  • - name (String)
  • - body_md (String)
  • - tags (Array of String)
  • - category (String)
  • - wip (Boolean)
  • - message (String)
  • - created_by(String)
  • - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の **作成者** を上書きすることができます。
  • - このパラメータは **team の owner** だけ が使用することができます。
  • - updated_by(String)
  • - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の **更新者** を上書きすることができます。
  • - このパラメータは **team の owner** だけ が使用することができます。
  • - original_revision (Object)
  • - body_md (String)
  • - 変更前の記事の本文です
  • - number (Integer)
  • - 変更前の記事のrevision_numberを指定します
  • - user (String)
  • - 変更前の記事の最終更新者のscreen_nameを指定します
  • ### original_revisionについて
  • リクエストに正常な `post.body_md` パラメータと `post.original_revision.*` パラメータが存在する場合、記事更新時に3 way mergeが行われます。original_revisionパラメータが存在しない場合は、変更は常に後勝ちになります。
  • > [release_note/2014/12/23/記事保存時の自動マージ - docs.esa.io](https://docs.esa.io/posts/35)
  • ```
  • PATCH /v1/teams/docs/posts/5 HTTP/1.1
  • Content-Type: application/json
  • {
  • "post":{
  • "name":"hi!",
  • "body_md":"# Getting Started\n",
  • "tags":[
  • "api",
  • "dev"
  • ],
  • "category":"dev/2015/05/10",
  • "wip":false,
  • "message":"Add Getting Started section",
  • "original_revision": {
  • "body_md": "# Getting ...",
  • "number":1,
  • "user": "fukayatsu"
  • }
  • }
  • }
  • ```
  • ```
  • HTTP/1.1 200 OK
  • Content-Type: application/json
  • {
  • "number": 5,
  • "name": "hi!",
  • "full_name": "日報/2015/05/10/hi! #api #dev",
  • "wip": false,
  • "body_md": "# Getting Started\n",
  • "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  • "created_at": "2015-05-09T12:12:37+09:00",
  • "message": "Add Getting Started section",
  • "url": "https://docs.esa.io/posts/5",
  • "updated_at": "2015-05-09T12:19:48+09:00",
  • "tags": [
  • "api",
  • "dev"
  • ],
  • "category": "日報/2015/05/10",
  • "revision_number": 2,
  • "created_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "updated_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "overlapped": false,
  • "kind": "flow",
  • "comments_count": 0,
  • "tasks_count": 0,
  • "done_tasks_count": 0,
  • "stargazers_count": 0,
  • "watchers_count": 1,
  • "star": false,
  • "watch": false
  • }
  • ```
  • - overlapped (Boolean)
  • - 3 way mergeを行いコンフリクトが起きた場合にのみ `true` になります。
  • ## DELETE /v1/teams/:team_name/posts/:post_number
  • 指定された投稿を削除します。
  • ```
  • DELETE /v1/teams/docs/posts/5 HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • # コメント
  • ユーザが作成したコメントを表します。
  • - id
  • - コメントを一意に識別するIDです
  • - Example: 123
  • - body_md (String)
  • - Markdownで書かれたコメントの本文です
  • - Example: `LGTM!`
  • - body_html (String)
  • - HTMLに変換されたコメントの本文です
  • - Example: `<p>LGTM!</p>`
  • - created_at (DateTime String)
  • - コメントが作成された日時です
  • - Example: `2014-05-10T12:45:42+09:00`
  • - updated_at (DateTime String)
  • - コメントが更新された日時です
  • - Example: `2014-05-18T23:02:29+09:00`
  • - url (String)
  • - コメントのpermalinkです
  • - https://docs.esa.io/posts/2#comment-123
  • - created_by (Object)
  • - コメントを作成したユーザを表します。
  • - name (String)
  • - ユーザ名です。
  • - Example: "Atsuo Fukaya"
  • - screen_name (String)
  • - ユーザを一意に識別するIDです。
  • - Example: "fukayatsu"
  • - icon (String)
  • - ユーザのアイコンのURLです。
  • - Exmaple: "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • ## GET /v1/teams/:team_name/posts/:post_number/comments
  • 記事のコメント一覧を更新日の降順で返却します。
  • ```
  • GET /v1/teams/docs/posts/2/comments HTTP/1.1
  • ```
  • ```
  • {
  • "comments": [
  • {
  • "id": 1,
  • "body_md": "(大事)",
  • "body_html": "<p>(大事)</p>",
  • "created_at": "2014-05-10T12:45:42+09:00",
  • "updated_at": "2014-05-18T23:02:29+09:00",
  • "url": "https://docs.esa.io/posts/2#comment-1",
  • "created_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "stargazers_count": 0,
  • "star": false
  • }
  • ],
  • "prev_page": null,
  • "next_page": null,
  • "total_count": 1,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • ## GET /v1/teams/:team_name/comments/:comment_id
  • 指定されたコメントを取得します。
  • ```
  • GET /v1/teams/docs/comments/13 HTTP/1.1
  • ```
  • ```
  • {
  • "id": 13,
  • "body_md": "読みたい",
  • "body_html": "<p>読みたい</p>",
  • "created_at": "2014-05-13T16:17:42+09:00",
  • "updated_at": "2014-05-18T23:02:29+09:00",
  • "url": "https://docs.esa.io/posts/13#comment-13",
  • "created_by": {
  • "name": "TAEKO AKATSUKA",
  • "screen_name": "taea",
  • "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg"
  • },
  • "stargazers_count": 0,
  • "star": false
  • }
  • ```
  • ## POST /v1/teams/:team_name/posts/:post_number/comments
  • 記事に新しいコメントを作成します。
  • - comment (Object)
  • - body_md (String, **required**)
  • - user(String)
  • - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
  • - このパラメータは **team の owner** だけ が使用することができます。
  • ```
  • POST /v1/teams/docs/posts/2/comments HTTP/1.1
  • Content-Type: application/json
  • {"comment":{"body_md":"LGTM!"}}
  • ```
  • ```
  • HTTP/1.1 201 Created
  • Content-Type: application/json
  • {
  • "id": 22767,
  • "body_md": "LGTM!",
  • "body_html": "<p>LGTM!</p>\n",
  • "created_at": "2015-06-21T19:36:20+09:00",
  • "updated_at": "2015-06-21T19:36:20+09:00",
  • "url": "https://docs.esa.io/posts/2#comment-22767",
  • "created_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "stargazers_count": 0,
  • "star": false
  • }
  • ```
  • ## PATCH /v1/teams/:team_name/comments/:comment_id
  • 指定されたコメントを更新します。
  • - comment (Object)
  • - body_md (String)
  • - user(String)
  • - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
  • - このパラメータは **team の owner** だけ が使用することができます。
  • ```
  • PATCH /v1/teams/docs/comments/22767 HTTP/1.1
  • Content-Type: application/json
  • {"comment":{"body_md":"LGTM!!!"}}
  • ```
  • ```
  • HTTP/1.1 200 OK
  • Content-Type: application/json
  • {
  • "id": 22767,
  • "body_md": "LGTM! :sushi:",
  • "body_html": "<p>LGTM!!!</p>\n",
  • "created_at": "2015-06-21T19:36:20+09:00",
  • "updated_at": "2015-06-21T19:40:33+09:00",
  • "url": "https://docs.esa.io/posts/2#comment-22767",
  • "created_by": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • },
  • "stargazers_count": 0,
  • "star": false
  • }
  • ```
  • ## DELETE /v1/teams/:team_name/comments/:comment_id
  • 指定されたコメントを削除します。
  • ```
  • DELETE /v1/teams/docs/comments/22767 HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • # Star
  • - created_at (DateTime)
  • - Starをした日時です。
  • - body (null or String)
  • - 引用Starの本文です。
  • - user
  • - Starをしたユーザです。
  • ## GET /v1/teams/:team_name/posts/:post_number/stargazers
  • 指定された記事にStarをしたユーザ一覧を取得します。
  • ```
  • GET /v1/teams/docs/posts/2312/stargazers HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 200 OK
  • Content-Type: application/json
  • {
  • "stargazers": [
  • {
  • "created_at": "2016-05-05T11:40:54+09:00",
  • "body": null,
  • "user": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • }
  • }
  • ],
  • "prev_page": null,
  • "next_page": null,
  • "total_count": 1,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • ## POST /v1/teams/:team_name/posts/:post_number/star
  • 指定された記事にStarをします。
  • - body (String)
  • - (任意) 引用Starの本文です。
  • ```
  • POST /v1/teams/docs/posts/2312/star HTTP/1.1
  • {"body":"foo bar"}
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • ## DELETE /v1/teams/:team_name/posts/:post_number/star
  • 指定された記事へのStarを取り消します。
  • ```
  • DELETE /v1/teams/docs/posts/2312/star HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • ## GET /v1/teams/:team_name/comments/:comment_id/stargazers
  • 指定されたコメントにStarをしたユーザ一覧を取得します。
  • ```
  • GET /v1/teams/docs/comments/123/stargazers HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 200 OK
  • Content-Type: application/json
  • {
  • "stargazers": [
  • {
  • "created_at": "2016-05-05T11:40:54+09:00",
  • "body": null,
  • "user": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • }
  • }
  • ],
  • "prev_page": null,
  • "next_page": null,
  • "total_count": 1,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • ## POST /v1/teams/:team_name/comments/:comment_id/star
  • 指定されたコメントにStarをします。
  • - body (String)
  • - (任意) 引用Starの本文です。
  • ```
  • POST /v1/teams/docs/comments/123/star HTTP/1.1
  • {"body":"foo bar"}
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • ## DELETE /v1/teams/:team_name/comments/:comment_id/star
  • 指定されたコメントへのStarを取り消します。
  • ```
  • DELETE /v1/teams/docs/comments/123/star HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • # Watch
  • - created_at (DateTime)
  • - Watchをした日時です。
  • - user
  • - Watchをしたユーザです。
  • ## GET /v1/teams/:team_name/posts/:post_number/watchers
  • 指定された記事にWatchをしたユーザ一覧を取得します。
  • ```
  • GET /v1/teams/docs/posts/2312/watchers HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 200 OK
  • Content-Type: application/json
  • {
  • "watchers": [
  • {
  • "created_at": "2016-05-05T11:40:53+09:00",
  • "user": {
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  • }
  • }
  • ],
  • "prev_page": null,
  • "next_page": null,
  • "total_count": 1,
  • "page": 1,
  • "per_page": 20,
  • "max_per_page": 100
  • }
  • ```
  • ## POST /v1/teams/:team_name/posts/:post_number/watch
  • 指定された記事にWatchをします。
  • ```
  • POST /v1/teams/docs/posts/2312/watch HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • ## DELETE /v1/teams/:team_name/posts/:post_number/watch
  • 指定された記事のWatchを取り消します。
  • ```
  • DELETE /v1/teams/docs/posts/2312/watch HTTP/1.1
  • ```
  • ```
  • HTTP/1.1 204 No Content
  • ```
  • # カテゴリ
  • ## POST v1/teams/:team_name/categories/batch_move
  • 指定されたカテゴリを配下のカテゴリを含めて一括で変更します。
  • - from (String)
  • - 変更元のカテゴリです
  • - to (String)
  • - 変更先のカテゴリです
  • ```
  • POST /v1/teams/docs/categories/batch_move HTTP/1.1
  • {
  • "from": "/foo/bar/",
  • "to": "/baz/"
  • }
  • ```
  • ```
  • HTTP/1.1 200 OK
  • {
  • "count": 3,
  • "from": "/foo/bar/",
  • "to": "/baz/"
  • }
  • ```
  • - count:
  • - サブカテゴリを含む変更されたカテゴリの数を表します
  • # 認証中のユーザ
  • ## GET /v1/user
  • 現在のアクセストークンで認証中のユーザの情報を表します。
  • - id (Integer)
  • - サービス内で一意なユーザIDです
  • - name (String)
  • - ユーザの名前です
  • - screen_name (String)
  • - ユーザのスクリーンネームです
  • - created_at (DateTime String)
  • - ユーザの作成日時です
  • - updated_at (DateTime String)
  • - ユーザの更新日時です
  • - icon (String)
  • - ユーザのアイコンのURLです
  • - email (String)
  • - ユーザのemailアドレスです
  • ### URIクエリ文字列
  • - include (String)
  • - `teams` を指定すると所属するチームの配列を含んだレスポンスを返します。
  • ```
  • GET /v1/user HTTP/1.1
  • Content-Type: application/json
  • ```
  • ```
  • HTTP/1.1 200 OK
  • Content-Type: application/json
  • {
  • "id": 1,
  • "name": "Atsuo Fukaya",
  • "screen_name": "fukayatsu",
  • "created_at": "2014-05-10T11:50:07+09:00",
  • "updated_at": "2016-04-17T12:35:16+09:00",
  • "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
  • "email": "fukayatsu@esa.io"
  • }
  • ```
  • # 他サービスからのインポートスクリプトのサンプル
  • ## Qiita:Team
  • ```import.rb
  • require 'esa'
  • require 'json'
  • require 'pp'
  • class Importer
  • def initialize(client, file_path)
  • @client = client
  • @exported_data = JSON.parse(File.read(file_path))
  • return if @exported_data['version'] == 'v1'
  • puts "This script is for Qiita Export Format v1. Please contact us (hi@esa.io) (\\( ⁰⊖⁰)/)"
  • exit 1
  • end
  • attr_accessor :client, :exported_data
  • def wait_for(seconds)
  • (seconds / 10).times do
  • print '.'
  • sleep 10
  • end
  • puts
  • end
  • def import(dry_run: true, start_index: 0)
  • users_map = {
  • "fukayatsu" => 'fukayatsu',
  • "ken_c_lo" => 'taea',
  • "foo" => 'bar',
  • }
  • exported_data['articles'].sort_by{ |article| article['updated_at'] }.each.with_index do |article, index|
  • next unless index >= start_index
  • params = {
  • name: article['title'],
  • category: "Imports/Qiita",
  • tags: article['tags'].map{ |tag| tag['name'].gsub('/', '-') },
  • body_md: <<-BODY_MD,
  • created_at: #{article['created_at']}
  • user: #{article['user']['id']}
  • #{article['body']}
  • BODY_MD
  • wip: false,
  • message: '[skip notice] Imported from Qiita',
  • user: users_map[article['user']['id']] || 'esa_bot', # 記事作成者上書き: owner権限が必要
  • }
  • if dry_run
  • puts "***** index: #{index} *****"
  • pp params
  • puts
  • next
  • end
  • print "[#{Time.now}] index[#{index}] #{article['title']} => "
  • response = client.create_post(params)
  • case response.status
  • when 201
  • puts "created: #{response.body["full_name"]}"
  • when 429
  • retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
  • puts "rate limit exceeded: will retry after #{retry_after} seconds."
  • wait_for(retry_after)
  • redo
  • else
  • puts "failure with status: #{response.status}"
  • exit 1
  • end
  • end
  • end
  • end
  • client = Esa::Client.new(
  • access_token: 'xxxxx',
  • current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
  • )
  • importer = Importer.new(client, '/path/to/exported_from_qiita_team.json')
  • # dry_run: trueで確認後に dry_run: falseで実際にimportを実行
  • importer.import(dry_run: true, start_index: 0)
  • ```
  • ## DocBase
  • ```ruby:import.rb
  • require 'esa'
  • require 'pp'
  • class Importer
  • def initialize(client, dir_path)
  • @client = client
  • @files = Dir.glob File.expand_path(File.join(dir_path, '*.md'))
  • end
  • attr_accessor :client, :files
  • def wait_for(seconds)
  • (seconds / 10).times do
  • print '.'
  • sleep 10
  • end
  • puts
  • end
  • def import(dry_run: true, start_index: 0)
  • files.sort_by { |file| File.basename(file, '.*').to_i }.each.with_index do |file, index|
  • next unless index >= start_index
  • params = {
  • name: File.basename(file, '.*'),
  • category: "Imports/DocBase",
  • body_md: File.read(file),
  • wip: false,
  • message: '[skip notice] Imported from DocBase',
  • user: 'esa_bot', # 記事作成者上書き: owner権限が必要
  • }
  • if dry_run
  • puts "***** index: #{index} *****"
  • pp params
  • puts
  • next
  • end
  • print "[#{Time.now}] index[#{index}] #{params['name']} => "
  • response = client.create_post(params)
  • case response.status
  • when 201
  • puts "created: #{response.body["full_name"]}"
  • when 429
  • retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
  • puts "rate limit exceeded: will retry after #{retry_after} seconds."
  • wait_for(retry_after)
  • redo
  • else
  • puts "failure with status: #{response.status}"
  • exit 1
  • end
  • end
  • end
  • end
  • client = Esa::Client.new(
  • access_token: 'xxxxx',
  • current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
  • )
  • importer = Importer.new(client, '/path/to/export_dir') # docbaseからexportしたzipを解答してできたフォルダのpath
  • # dry_run: trueで確認後に dry_run: falseで実際にimportを実行
  • importer.import(dry_run: true, start_index: 0)
  • ```
  • ## GitHub Wiki
  • ```ruby:import.rb
  • require 'esa'
  • require 'json'
  • require 'pp'
  • require 'pry'
  • class Importer
  • def initialize(client, wiki_repo_url)
  • @client = client
  • `git clone #{wiki_repo_url} wiki_data` unless File.exist? 'wiki_data'
  • Dir.chdir('wiki_data')
  • @file_names = Dir.glob('*.md')
  • end
  • attr_accessor :client, :file_names
  • def wait_for(seconds)
  • (seconds / 10).times do
  • print '.'
  • sleep 10
  • end
  • puts
  • end
  • def import(dry_run: true)
  • file_names.each do |file_name|
  • title = File.basename(file_name)
  • params = {
  • name: title,
  • category: "Imports/GitHubWiki",
  • body_md: File.read(file_name),
  • wip: false,
  • message: '[skip notice] Imported from GitHub Wiki',
  • user: 'esa_bot' # 記事作成者上書き: owner権限が必要
  • }
  • if dry_run
  • pp params
  • puts
  • next
  • end
  • print "[#{Time.now}] #{title} => "
  • response = client.create_post(params)
  • case response.status
  • when 201
  • puts "created: #{response.body["full_name"]}"
  • when 429
  • retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
  • puts "rate limit exceeded: will retry after #{retry_after} seconds."
  • wait_for(retry_after)
  • redo
  • else
  • puts "failure with status: #{response.status}"
  • exit 1
  • end
  • end
  • end
  • end
  • client = Esa::Client.new(
  • access_token: 'xxxxx',
  • current_team: 'your-team-name' # 移行先のチーム名(サブドメイン)
  • )
  • importer = Importer.new(client, 'https://github.com/[organization]/[repo].wiki.git')
  • # dry_run: trueで確認後に dry_run: falseで実際にimportを実行
  • importer.import(dry_run: true)
  • ```
  • # 今後の実装予定
  • - Preview API
  • - Revision API

はじめに

この記事では esa API v1について説明します。

概要

リクエスト

APIとの通信には HTTPS プロトコルを使用します。アクセス先のホストは api.esa.io です。

パラメータ

API v1へのリクエストには、 GET / POST / PUT / PATCH / DELETE の HTTPメソッドを利用します。
多くのAPIはリクエスト時にパラメータを含められますが、GETリクエストにはURIクエリ文字列を利用し、それ以外の場合にはリクエストボディを利用します。パラメータにはページネーション等の任意で渡すものと、記事名等の必須のものが存在します。

利用制限

現時点では、ユーザ毎に15分間に75リクエストまで受け付けます。

制限状況はAPIのレスポンスヘッダに含まれる X-RateLimit-* の値をご確認下さい。

X-RateLimit-Limit: 75
X-RateLimit-Remaining: 73
X-RateLimit-Limit: 75
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1491543000

なお、決められた制限を超える場合は、429 Too Many Requests が返却されます。

ステータスコード

200 / 201 / 204 / 400 / 401 / 402 / 403 / 404 / 500 のステータスコードを利用します。

データ形式

APIとのデータの送受信にはJSONを利用します。JSONをリクエストボディに含める場合はContent-Type: application/jsonをリクエストヘッダに追加して下さい。文字コードはUTF-8を使用して下さい。

エラーレスポンス

エラーが発生した場合は、エラーを表現するオブジェクトを含んだエラーレスポンスが返却されます。

json
{
  "error": "not_found",
  "message": "Not found"
}

ページネーション

リストを返すAPIでは、すべての要素を一度に返すことは現実的ではないため、ページを指定することができるようになっています。これらのAPIには、1から始まるページ番号を表す page パラメータと、1ページあたりに含まれる要素数を表す per_page パラメータを指定することができます。page の初期値は 1 に設定されています。また、 per_page の初期値は 20、最大値は 100 に設定されています。

ページを指定できるAPIでは、ページネーションの情報を含んだレスポンスを返します。

# GET /v1/teams HTTP/1.1

{
    "teams": [
        ...
    ],
    "prev_page": null,
    "next_page": 2,
    "total_count": 30,
    "page": 1,
    "per_page": 20,
    "max_per_page": 100
}
  • prev_page
    • 1つ前のpage番号。存在しない場合はnull
  • next_page
    • 1つ先のpage番号。存在しない場合はnull
  • total_count
    • リソースの総数
  • page
    • 現在のページ番号
  • per_page
    • 1ページあたりに含まれる要素数
  • max_per_page
    • per_pageに指定可能な数の最大値

クライアントライブラリ

現時点で以下のライブラリが利用可能です。

認証と認可

esa APIを利用するには、アクセストークンをリクエストに含める必要があります。アクセストークンは、ユーザの管理画面(https://[team].esa.io/user/tokens)もしくはOAuthを利用した認可フローにより発行できます。

アクセストークン

アクセストークンは、以下のようにリクエストヘッダに含められます。

Authorization: Bearer 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef

また、URIクエリ文字列として指定することも可能です。

api.esa.io/v1/teams?access_token=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef

スコープ

個々のアクセストークンには、複数のスコープを紐付けることができます。アクセストークンが適切なスコープを持っている時のみAPIを利用することができます。esa APIでは以下のスコープを利用することができます。

スコープ 説明
read データの読み出し
write データの書き込み

OAuthを利用した認可フロー

アプリケーションの登録

https://[team].esa.io/user/applications から アプリケーションを登録できます。

※ RailsやSinatraでは、以下の手順は omniauth-esa を使うことで実装の手間を省くことが出来ます。

GET /oauth/authorize

アプリケーションのユーザーに認可画面を表示します。

  • client_id
    • 登録されたアプリケーションのclient_idです
  • redirect_uri
    • 認可が行われた後のリダイレクト先を指定します。アプリケーション登録時に設定したredirect_uriのうちいずれかに一致する必要があります。
    • urn:ietf:wg:oauth:2.0:oobを指定した場合、認可後にリダイレクトせず画面に認証コードを表示します
  • scope
    • アプリケーションが利用するスコープをスペース区切りで指定します
  • response_type
    • code を指定します
  • state
    • CSRF対策のため、認可後にリダイレクトするURLのクエリに含まれる値を指定できます
GET /oauth/authorize?client_id=0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=read+write&state=a7e567e2fb858f0e12838798016ee9cf8ccc778 HTTP/1.1

ss 2016-04-21 14.49.33.png (138.0 kB)

POST /oauth/token

新規にアクセストークンを発行します。

  • client_id
    • 登録されたアプリケーションのClient IDです
  • client_secret
    • 登録されたアプリケーションのClient Secretです
  • grant_type
    • authorization_code を指定します
  • redirect_uri
    • 認可を行った際のredirect_uriを指定します
  • code
    • 認可後のリダイレクト先のURLに含まれた認可コードです
POST /oauth/token HTTP/1.1

{"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","code":"c486e26492dfdc42bcf4e07bfbd84a97f98548c1cf89178a512e76b79b099313","grant_type":"authorization_code","redirect_uri":"urn:ietf:wg:oauth:2.0:oob"}
HTTP/1.1 200 OK

{
  "access_token": "5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc",
  "token_type": "bearer",
  "scope": "read write",
  "created_at": 1461218696
}

GET /oauth/token/info

アクセストークンの情報を取得します

GET /oauth/token/info HTTP/1.1
Authorization: bearer 5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc
HTTP/1.1 200 OK

{"resource_owner_id":1,"scopes":["read","write"],"expires_in_seconds":null,"application":{"uid":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04"},"created_at":1461218696}

POST /oauth/revoke

アクセストークンを失効させます

  • client_id
    • 登録されたアプリケーションのClient IDです
  • client_secret
    • 登録されたアプリケーションのClient Secretです
  • token
    • アクセストークンを指定します
POST /oauth/revoke HTTP/1.1

{"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","token":"5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc"}
HTTP/1.1 200 OK

{}

チーム

esa上で所属しているチームを表します。

  • name (String)
    • チームを特定するための一意なIDです。サブドメインになります。
    • Example: "docs"
  • privacy (String)
    • チームの公開範囲です。
      • closed: "チームメンバーだけが情報にアクセスできます。"
      • open: "ShipItされた記事はインターネット上に公開されます。"
  • description (String)
    • チームの説明です
    • Example: "esa.io official documents"
    • 登録がない場合には空文字列("")になります
  • icon (String)

MEMO: member_count等の情報を追加予定

GET /v1/teams

GET /v1/teams HTTP/1.1
{
  "teams": [
    {
      "name": "docs",
      "privacy": "open",
      "description": "esa.io official documents",
      "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
      "url": "https://docs.esa.io/"
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

GET /v1/teams/:team_name

GET /v1/teams/docs HTTP/1.1
{
  "name": "docs",
  "privacy": "open",
  "description": "esa.io official documents",
  "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
  "url": "https://docs.esa.io/"
}

統計情報

チームの統計情報を表します。

  • members(Integer)
    • チーム内のメンバーの総数です
  • posts(Integer)
    • チーム内の記事の総数です
  • posts_wip(Integer)
    • チーム内の記事(wip)の総数です
  • posts_shipped(Integer)
    • チーム内の記事(shipped)の総数です
  • comments(Integer)
    • チーム内の記事につけられたコメントの総数です
  • stars(Integer)
    • チーム内の記事につけられたスターの総数です
  • daily_active_users(Integer)
    • 過去24時間以内に記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
  • weekly_active_users(Integer)
    • 過去7日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
  • monthly_active_users(Integer)
    • 過去30日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。

GET /v1/teams/:team_name/stats

GET /v1/teams/foo/stats HTTP/1.1
{
  "members": 20,
  "posts": 1959,
  "posts_wip": 59,
  "posts_shipped": 1900,
  "comments": 2695,
  "stars": 3115,
  "daily_active_users": 8,
  "weekly_active_users": 14,
  "monthly_active_users": 15
}

メンバー

チームのメンバーを表します。

  • name (String)
    • メンバーの名前です
  • screen_name (String)
    • メンバーのスクリーンネームです
  • icon (String)
    • メンバーのアイコンのURLです
  • email (String)
    • メンバーのemailです
    • このフィールドは team の owner だけが取得可能です
  • posts_count (Integer)
    • チーム内でメンバーが作成した記事数です

GET /v1/teams/:team_name/members

GET /v1/teams/foo/members HTTP/1.1
{
  "members": [
    {
      "name": "Atsuo Fukaya",
      "screen_name": "fukayatsu",
      "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
      "email": "fukayatsu@esa.io",
      "posts_count": 222
    },
    {
      "name": "TAEKO AKATSUKA",
      "screen_name": "taea",
      "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg",
      "email": "taea@esa.io",
      "posts_count": 111
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 2,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

記事

ユーザが作成した記事を表します。

  • number (Integer)
    • チーム内で記事を特定するためのIDです
    • Example: 123
  • name (String)
    • 記事名です。タグやカテゴリー部分は含みません。
    • Example: "hi!"
  • tags (Array of String)
    • 記事に紐付けられたタグです。
    • Example: ["api", "dev"]
  • category (String)
    • 記事が属するカテゴリです
    • Example: "日報/2015/05/09"
    • 設定されていない場合は null になります
  • full_name (String)
    • カテゴリとタグを含む、記事名です。
    • Example: "日報/2015/05/09/hi! #api #dev"
  • wip (Boolean)
    • 記事がWIP(Working In Progress)状態かどうかを表します。
    • Example: "true"
  • body_md (String)
    • Markdownで書かれた記事の本文です
    • Example "# Getting Started"
  • body_html (String)
    • HTMLに変換された記事の本文です。
    • Example: <h1>Getting Started</h1>
  • created_at (DateTime String)
    • 記事が作成された日時です
    • Example: "2014-05-10T12:08:55+09:00"
  • updated_at (DateTime String)
    • 記事が更新された日時です。
    • Example: "2014-05-11T19:21:00+09:00"
  • message (String)
    • 記事更新時の変更メモです。
    • Example: "Add Getting Started section"
  • revision_number(Integer)
    • 記事のリビジョン番号です。
    • Example: 47
  • created_by (Object)
  • updated_by (Object)
    • 記事を最後に更新したユーザを表します。フィールドは created_by と共通です。
    • name (String)
    • screen_name (String)
    • icon (String)
  • kind(String, "stock" or "flow")
    • 記事の種別を表します
  • comments_count(Integer)
    • 記事へのコメント数を表します
    • Example: 1
  • tasks_count(Integer)
    • 記事中のタスクの総数を表します
    • Example: 1
  • done_tasks_count(Integer)
    • 記事中の完了済みのタスクの総数を表します
    • Example: 1
  • stargazers_count(Integer)
    • 記事にStarをしている人数を表します
    • Example: 1
  • watchers_count(Integer)
    • 記事をWatchしている人数を表します
    • Example: 1
  • star(Boolean)
    • ユーザーが記事をStarしているかどうかを表します
    • Example: true
  • watch(Boolean)
    • ユーザーが記事をWatchしているかどうかを表します
    • Example: true

GET /v1/teams/:team_name/posts

記事の一覧を更新日の降順で返却します

GET /v1/teams/docs/posts HTTP/1.1

URIクエリ文字列

  • q (String)
  • include (String)
    • comments を指定するとコメントの配列を含んだレスポンスを返します。
    • stargazers を指定するとStarの配列を含んだレスポンスを返します。
    • stargazers,comments のように , で区切ることで複数指定できます
  • sort (String)
    • 設定可能な値
      • updated (default)
        • 記事の更新日時
      • created
        • 記事の作成日時
      • stars
        • 記事へのStarの数
      • watches
        • 記事へのWatchの数
      • comments
        • 記事へのCommentの数
      • best_match
        • 総合的な記事のスコア
  • order (String)
    • 設定可能な値
      • desc (default)
        • 降順
      • asc
        • 昇順
{
  "posts": [
    {
      "number": 1,
      "name": "hi!",
      "full_name": "日報/2015/05/09/hi! #api #dev",
      "wip": true,
      "body_md": "# Getting Started",
      "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
      "created_at": "2015-05-09T11:54:50+09:00",
      "message": "Add Getting Started section",
      "url": "https://docs.esa.io/posts/1",
      "updated_at": "2015-05-09T11:54:51+09:00",
      "tags": [
        "api",
        "dev"
      ],
      "category": "日報/2015/05/09",
      "revision_number": 1,
      "created_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      },
      "updated_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

GET /v1/teams/:team_name/posts/:post_number

指定された投稿を取得します。

URIクエリ文字列

  • include (String)
    • comments を指定するとコメントの配列を含んだレスポンスを返します。
GET /v1/teams/docs/posts/1 HTTP/1.1
{
  "number": 1,
  "name": "hi!",
  "full_name": "日報/2015/05/09/hi! #api #dev",
  "wip": true,
  "body_md": "# Getting Started",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T11:54:50+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/1",
  "updated_at": "2015-05-09T11:54:51+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "日報/2015/05/09",
  "revision_number": 1,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "kind": "flow",
  "comments_count": 1,
  "tasks_count": 1,
  "done_tasks_count": 1,
  "stargazers_count": 1,
  "watchers_count": 1,
  "star": true,
  "watch": true
}

POST /v1/teams/:team_name/posts

記事を新たに投稿します。

  • post (Object)
    • name (String, required)
    • body_md (String)
    • tags (Array of String)
    • category (String)
    • wip (Boolean, default: true)
    • message (String)
    • user(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
POST /v1/teams/docs/posts HTTP/1.1
Content-Type: application/json

{
   "post":{
      "name":"hi!",
      "body_md":"# Getting Started\n",
      "tags":[
         "api",
         "dev"
      ],
      "category":"dev/2015/05/10",
      "wip":false,
      "message":"Add Getting Started section"
   }
}
HTTP/1.1 201 Created
Content-Type: application/json

{
  "number": 5,
  "name": "hi!",
  "full_name": "dev/2015/05/10/hi! #api #dev",
  "wip": false,
  "body_md": "# Getting Started\n",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T12:12:37+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/5",
  "updated_at": "2015-05-09T12:12:37+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "dev/2015/05/10",
  "revision_number": 1,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "kind": "flow",
  "comments_count": 0,
  "tasks_count": 0,
  "done_tasks_count": 0,
  "stargazers_count": 0,
  "watchers_count": 1,
  "star": false,
  "watch": false
}

PATCH /v1/teams/:team_name/posts/:post_number

指定された投稿を編集します。

  • post (Object)
    • name (String)
    • body_md (String)
    • tags (Array of String)
    • category (String)
    • wip (Boolean)
    • message (String)
    • created_by(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の 作成者 を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
    • updated_by(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の 更新者 を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
    • original_revision (Object)
      • body_md (String)
        • 変更前の記事の本文です
      • number (Integer)
        • 変更前の記事のrevision_numberを指定します
      • user (String)
        • 変更前の記事の最終更新者のscreen_nameを指定します

original_revisionについて

リクエストに正常な post.body_md パラメータと post.original_revision.* パラメータが存在する場合、記事更新時に3 way mergeが行われます。original_revisionパラメータが存在しない場合は、変更は常に後勝ちになります。

release_note/2014/12/23/記事保存時の自動マージ - docs.esa.io

PATCH /v1/teams/docs/posts/5 HTTP/1.1
Content-Type: application/json

{
   "post":{
      "name":"hi!",
      "body_md":"# Getting Started\n",
      "tags":[
         "api",
         "dev"
      ],
      "category":"dev/2015/05/10",
      "wip":false,
      "message":"Add Getting Started section",
      "original_revision": {
          "body_md": "# Getting ...",
          "number":1,
          "user": "fukayatsu"
        }
    }
}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "number": 5,
  "name": "hi!",
  "full_name": "日報/2015/05/10/hi! #api #dev",
  "wip": false,
  "body_md": "# Getting Started\n",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T12:12:37+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/5",
  "updated_at": "2015-05-09T12:19:48+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "日報/2015/05/10",
  "revision_number": 2,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "overlapped": false,
  "kind": "flow",
  "comments_count": 0,
  "tasks_count": 0,
  "done_tasks_count": 0,
  "stargazers_count": 0,
  "watchers_count": 1,
  "star": false,
  "watch": false
}
  • overlapped (Boolean)
    • 3 way mergeを行いコンフリクトが起きた場合にのみ true になります。

DELETE /v1/teams/:team_name/posts/:post_number

指定された投稿を削除します。

DELETE /v1/teams/docs/posts/5 HTTP/1.1
HTTP/1.1 204 No Content

コメント

ユーザが作成したコメントを表します。

  • id
    • コメントを一意に識別するIDです
    • Example: 123
  • body_md (String)
    • Markdownで書かれたコメントの本文です
    • Example: LGTM!
  • body_html (String)
    • HTMLに変換されたコメントの本文です
    • Example: <p>LGTM!</p>
  • created_at (DateTime String)
    • コメントが作成された日時です
    • Example: 2014-05-10T12:45:42+09:00
  • updated_at (DateTime String)
    • コメントが更新された日時です
    • Example: 2014-05-18T23:02:29+09:00
  • url (String)
  • created_by (Object)

GET /v1/teams/:team_name/posts/:post_number/comments

記事のコメント一覧を更新日の降順で返却します。

GET /v1/teams/docs/posts/2/comments HTTP/1.1
{
  "comments": [
    {
      "id": 1,
      "body_md": "(大事)",
      "body_html": "<p>(大事)</p>",
      "created_at": "2014-05-10T12:45:42+09:00",
      "updated_at": "2014-05-18T23:02:29+09:00",
      "url": "https://docs.esa.io/posts/2#comment-1",
      "created_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      },
      "stargazers_count": 0,
      "star": false
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

GET /v1/teams/:team_name/comments/:comment_id

指定されたコメントを取得します。

GET /v1/teams/docs/comments/13 HTTP/1.1
{
  "id": 13,
  "body_md": "読みたい",
  "body_html": "<p>読みたい</p>",
  "created_at": "2014-05-13T16:17:42+09:00",
  "updated_at": "2014-05-18T23:02:29+09:00",
  "url": "https://docs.esa.io/posts/13#comment-13",
  "created_by": {
    "name": "TAEKO AKATSUKA",
    "screen_name": "taea",
    "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg"
  },
  "stargazers_count": 0,
  "star": false
}

POST /v1/teams/:team_name/posts/:post_number/comments

記事に新しいコメントを作成します。

  • comment (Object)
    • body_md (String, required)
    • user(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
POST /v1/teams/docs/posts/2/comments HTTP/1.1
Content-Type: application/json

{"comment":{"body_md":"LGTM!"}}
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": 22767,
  "body_md": "LGTM!",
  "body_html": "<p>LGTM!</p>\n",
  "created_at": "2015-06-21T19:36:20+09:00",
  "updated_at": "2015-06-21T19:36:20+09:00",
  "url": "https://docs.esa.io/posts/2#comment-22767",
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "stargazers_count": 0,
  "star": false
}

PATCH /v1/teams/:team_name/comments/:comment_id

指定されたコメントを更新します。

  • comment (Object)
    • body_md (String)
    • user(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
PATCH /v1/teams/docs/comments/22767 HTTP/1.1
Content-Type: application/json

{"comment":{"body_md":"LGTM!!!"}}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 22767,
  "body_md": "LGTM! :sushi:",
  "body_html": "<p>LGTM!!!</p>\n",
  "created_at": "2015-06-21T19:36:20+09:00",
  "updated_at": "2015-06-21T19:40:33+09:00",
  "url": "https://docs.esa.io/posts/2#comment-22767",
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "stargazers_count": 0,
  "star": false
}

DELETE /v1/teams/:team_name/comments/:comment_id

指定されたコメントを削除します。

DELETE /v1/teams/docs/comments/22767 HTTP/1.1
HTTP/1.1 204 No Content

Star

  • created_at (DateTime)
    • Starをした日時です。
  • body (null or String)
    • 引用Starの本文です。
  • user
    • Starをしたユーザです。

GET /v1/teams/:team_name/posts/:post_number/stargazers

指定された記事にStarをしたユーザ一覧を取得します。

GET /v1/teams/docs/posts/2312/stargazers HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{
  "stargazers": [
    {
      "created_at": "2016-05-05T11:40:54+09:00",
      "body": null,
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

POST /v1/teams/:team_name/posts/:post_number/star

指定された記事にStarをします。

  • body (String)
    • (任意) 引用Starの本文です。
POST /v1/teams/docs/posts/2312/star HTTP/1.1

{"body":"foo bar"}
HTTP/1.1 204 No Content

DELETE /v1/teams/:team_name/posts/:post_number/star

指定された記事へのStarを取り消します。

DELETE /v1/teams/docs/posts/2312/star HTTP/1.1
HTTP/1.1 204 No Content

GET /v1/teams/:team_name/comments/:comment_id/stargazers

指定されたコメントにStarをしたユーザ一覧を取得します。

GET /v1/teams/docs/comments/123/stargazers HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{
  "stargazers": [
    {
      "created_at": "2016-05-05T11:40:54+09:00",
      "body": null,
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

POST /v1/teams/:team_name/comments/:comment_id/star

指定されたコメントにStarをします。

  • body (String)
    • (任意) 引用Starの本文です。
POST /v1/teams/docs/comments/123/star HTTP/1.1

{"body":"foo bar"}
HTTP/1.1 204 No Content

DELETE /v1/teams/:team_name/comments/:comment_id/star

指定されたコメントへのStarを取り消します。

DELETE /v1/teams/docs/comments/123/star HTTP/1.1
HTTP/1.1 204 No Content

Watch

  • created_at (DateTime)
    • Watchをした日時です。
  • user
    • Watchをしたユーザです。

GET /v1/teams/:team_name/posts/:post_number/watchers

指定された記事にWatchをしたユーザ一覧を取得します。

GET /v1/teams/docs/posts/2312/watchers HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{
  "watchers": [
    {
      "created_at": "2016-05-05T11:40:53+09:00",
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

POST /v1/teams/:team_name/posts/:post_number/watch

指定された記事にWatchをします。

POST /v1/teams/docs/posts/2312/watch HTTP/1.1
HTTP/1.1 204 No Content

DELETE /v1/teams/:team_name/posts/:post_number/watch

指定された記事のWatchを取り消します。

DELETE /v1/teams/docs/posts/2312/watch HTTP/1.1
HTTP/1.1 204 No Content

カテゴリ

POST v1/teams/:team_name/categories/batch_move

指定されたカテゴリを配下のカテゴリを含めて一括で変更します。

  • from (String)
    • 変更元のカテゴリです
  • to (String)
    • 変更先のカテゴリです
POST /v1/teams/docs/categories/batch_move HTTP/1.1

{
  "from": "/foo/bar/",
  "to": "/baz/"
}

HTTP/1.1 200 OK

{
  "count": 3,
  "from": "/foo/bar/",
  "to": "/baz/"
}
  • count:
    • サブカテゴリを含む変更されたカテゴリの数を表します

認証中のユーザ

GET /v1/user

現在のアクセストークンで認証中のユーザの情報を表します。

  • id (Integer)
    • サービス内で一意なユーザIDです
  • name (String)
    • ユーザの名前です
  • screen_name (String)
    • ユーザのスクリーンネームです
  • created_at (DateTime String)
    • ユーザの作成日時です
  • updated_at (DateTime String)
    • ユーザの更新日時です
  • icon (String)
    • ユーザのアイコンのURLです
  • email (String)
    • ユーザのemailアドレスです

URIクエリ文字列

  • include (String)
    • teams を指定すると所属するチームの配列を含んだレスポンスを返します。
GET /v1/user HTTP/1.1
Content-Type: application/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 1,
  "name": "Atsuo Fukaya",
  "screen_name": "fukayatsu",
  "created_at": "2014-05-10T11:50:07+09:00",
  "updated_at": "2016-04-17T12:35:16+09:00",
  "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
  "email": "fukayatsu@esa.io"
}

他サービスからのインポートスクリプトのサンプル

Qiita:Team

import.rb
require 'esa'
require 'json'
require 'pp'

class Importer
  def initialize(client, file_path)
    @client = client
    @exported_data = JSON.parse(File.read(file_path))

    return if @exported_data['version'] == 'v1'

    puts "This script is for Qiita Export Format v1. Please contact us (hi@esa.io) (\\( ⁰⊖⁰)/)"
    exit 1
  end
  attr_accessor :client, :exported_data

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true, start_index: 0)
    users_map = {
      "fukayatsu" => 'fukayatsu',
      "ken_c_lo"  => 'taea',
      "foo"       => 'bar',
    }

    exported_data['articles'].sort_by{ |article| article['updated_at'] }.each.with_index do |article, index|
      next unless index >= start_index

      params = {
        name:     article['title'],
        category: "Imports/Qiita",
        tags:     article['tags'].map{ |tag| tag['name'].gsub('/', '-') },
        body_md:  <<-BODY_MD,
created_at: #{article['created_at']}
user: #{article['user']['id']}

#{article['body']}
BODY_MD
        wip:      false,
        message:  '[skip notice] Imported from Qiita',
        user:     users_map[article['user']['id']] || 'esa_bot',  # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        puts "***** index: #{index} *****"
        pp params
        puts
        next
      end

      print "[#{Time.now}] index[#{index}] #{article['title']} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx', 
  current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, '/path/to/exported_from_qiita_team.json')

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true, start_index: 0) 

DocBase

import.rb
require 'esa'
require 'pp'

class Importer
  def initialize(client, dir_path)
    @client = client
    @files = Dir.glob File.expand_path(File.join(dir_path, '*.md'))
  end
  attr_accessor :client, :files

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true, start_index: 0)
     files.sort_by { |file| File.basename(file, '.*').to_i }.each.with_index do |file, index|
      next unless index >= start_index

      params = {
        name:     File.basename(file, '.*'),
        category: "Imports/DocBase",
        body_md:  File.read(file),
        wip:      false,
        message:  '[skip notice] Imported from DocBase',
        user:     'esa_bot',  # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        puts "***** index: #{index} *****"
        pp params
        puts
        next
      end

      print "[#{Time.now}] index[#{index}] #{params['name']} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx',
  current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, '/path/to/export_dir') # docbaseからexportしたzipを解答してできたフォルダのpath

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true, start_index: 0)

GitHub Wiki

import.rb
require 'esa'
require 'json'
require 'pp'
require 'pry'

class Importer
  def initialize(client, wiki_repo_url)
    @client = client

    `git clone #{wiki_repo_url} wiki_data` unless File.exist? 'wiki_data'
    Dir.chdir('wiki_data')
    @file_names = Dir.glob('*.md')
  end
  attr_accessor :client, :file_names

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true)
    file_names.each do |file_name|

      title = File.basename(file_name)

      params = {
        name:     title,
        category: "Imports/GitHubWiki",
        body_md:  File.read(file_name),
        wip:      false,
        message:  '[skip notice] Imported from GitHub Wiki',
        user:     'esa_bot' # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        pp params
        puts
        next
      end

      print "[#{Time.now}] #{title} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx',
  current_team: 'your-team-name' # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, 'https://github.com/[organization]/[repo].wiki.git')

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true)

今後の実装予定

  • Preview API
  • Revision API
# はじめに

この記事では esa API v1について説明します。

# 概要

## リクエスト

APIとの通信には `HTTPS` プロトコルを使用します。アクセス先のホストは `api.esa.io` です。

## パラメータ

API v1へのリクエストには、 `GET` / `POST` / `PUT` / `PATCH` / `DELETE` の HTTPメソッドを利用します。
多くのAPIはリクエスト時にパラメータを含められますが、GETリクエストにはURIクエリ文字列を利用し、それ以外の場合にはリクエストボディを利用します。パラメータにはページネーション等の任意で渡すものと、記事名等の必須のものが存在します。

## 利用制限

現時点では、ユーザ毎に15分間に75リクエストまで受け付けます。

制限状況はAPIのレスポンスヘッダに含まれる `X-RateLimit-*` の値をご確認下さい。

```
X-RateLimit-Limit: 75
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1491543000
```

なお、決められた制限を超える場合は、`429 Too Many Requests` が返却されます。

## ステータスコード

`200` / `201` / `204` / `400` / `401` / `402` / `403` / `404` / `500` のステータスコードを利用します。

## データ形式

APIとのデータの送受信にはJSONを利用します。JSONをリクエストボディに含める場合は`Content-Type: application/json`をリクエストヘッダに追加して下さい。文字コードはUTF-8を使用して下さい。

## エラーレスポンス

エラーが発生した場合は、エラーを表現するオブジェクトを含んだエラーレスポンスが返却されます。

```json
{
  "error": "not_found",
  "message": "Not found"
}
```

## ページネーション

リストを返すAPIでは、すべての要素を一度に返すことは現実的ではないため、ページを指定することができるようになっています。これらのAPIには、1から始まるページ番号を表す `page` パラメータと、1ページあたりに含まれる要素数を表す `per_page` パラメータを指定することができます。`page` の初期値は `1` に設定されています。また、 `per_page` の初期値は `20`、最大値は `100` に設定されています。

ページを指定できるAPIでは、ページネーションの情報を含んだレスポンスを返します。

```
# GET /v1/teams HTTP/1.1

{
    "teams": [
        ...
    ],
    "prev_page": null,
    "next_page": 2,
    "total_count": 30,
    "page": 1,
    "per_page": 20,
    "max_per_page": 100
}
```

- prev_page
    - 1つ前のpage番号。存在しない場合はnull
- next_page
    - 1つ先のpage番号。存在しない場合はnull
- total_count
    - リソースの総数
- page
    - 現在のページ番号
- per_page
    - 1ページあたりに含まれる要素数
- max_per_page
    - per_pageに指定可能な数の最大値

## クライアントライブラリ

現時点で以下のライブラリが利用可能です。

- Ruby
    - [esaio/esa-ruby](https://github.com/esaio/esa-ruby)

# 認証と認可

esa APIを利用するには、アクセストークンをリクエストに含める必要があります。アクセストークンは、ユーザの管理画面(https://[team].esa.io/user/tokens)もしくはOAuthを利用した認可フローにより発行できます。


## アクセストークン

アクセストークンは、以下のようにリクエストヘッダに含められます。

```
Authorization: Bearer 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
```

また、URIクエリ文字列として指定することも可能です。

```
api.esa.io/v1/teams?access_token=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
```

## スコープ

個々のアクセストークンには、複数のスコープを紐付けることができます。アクセストークンが適切なスコープを持っている時のみAPIを利用することができます。esa APIでは以下のスコープを利用することができます。

| スコープ | 説明 |
| --- | --- |
| read | データの読み出し |
| write | データの書き込み |

## OAuthを利用した認可フロー

### アプリケーションの登録

`https://[team].esa.io/user/applications` から アプリケーションを登録できます。

※ RailsやSinatraでは、以下の手順は [omniauth-esa](https://github.com/esaio/omniauth-esa) を使うことで実装の手間を省くことが出来ます。

### GET /oauth/authorize

アプリケーションのユーザーに認可画面を表示します。

- client_id
    - 登録されたアプリケーションのclient_idです
- redirect_uri
    - 認可が行われた後のリダイレクト先を指定します。アプリケーション登録時に設定したredirect_uriのうちいずれかに一致する必要があります。
    - `urn:ietf:wg:oauth:2.0:oob`を指定した場合、認可後にリダイレクトせず画面に認証コードを表示します
- scope
    - アプリケーションが利用するスコープをスペース区切りで指定します
- response_type
    - `code` を指定します
- state
    - CSRF対策のため、認可後にリダイレクトするURLのクエリに含まれる値を指定できます

```
GET /oauth/authorize?client_id=0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=read+write&state=a7e567e2fb858f0e12838798016ee9cf8ccc778 HTTP/1.1
```

<img width="578" alt="ss 2016-04-21 14.49.33.png (138.0 kB)" src="https://img.esa.io/uploads/production/attachments/105/2016/04/21/1/781dc446-c038-49aa-81f0-7486d9a4e265.png">

### POST /oauth/token

新規にアクセストークンを発行します。

- client_id
    - 登録されたアプリケーションのClient IDです
- client_secret
    - 登録されたアプリケーションのClient Secretです
- grant_type
    - `authorization_code ` を指定します
- redirect_uri
    - 認可を行った際のredirect_uriを指定します
- code
    - 認可後のリダイレクト先のURLに含まれた認可コードです

```
POST /oauth/token HTTP/1.1

{"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","code":"c486e26492dfdc42bcf4e07bfbd84a97f98548c1cf89178a512e76b79b099313","grant_type":"authorization_code","redirect_uri":"urn:ietf:wg:oauth:2.0:oob"}
```

```
HTTP/1.1 200 OK

{
  "access_token": "5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc",
  "token_type": "bearer",
  "scope": "read write",
  "created_at": 1461218696
}
```

### GET /oauth/token/info

アクセストークンの情報を取得します

```
GET /oauth/token/info HTTP/1.1
Authorization: bearer 5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc
```

```
HTTP/1.1 200 OK

{"resource_owner_id":1,"scopes":["read","write"],"expires_in_seconds":null,"application":{"uid":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04"},"created_at":1461218696}
```

### POST /oauth/revoke

アクセストークンを失効させます

- client_id
    - 登録されたアプリケーションのClient IDです
- client_secret
    - 登録されたアプリケーションのClient Secretです
- token
    - アクセストークンを指定します

```
POST /oauth/revoke HTTP/1.1

{"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","token":"5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc"}
```

```
HTTP/1.1 200 OK

{}
```

# チーム

esa上で所属しているチームを表します。

- name (String)
    - チームを特定するための一意なIDです。サブドメインになります。
    - Example: "docs"
- privacy (String)
    - チームの公開範囲です。
        - closed: "チームメンバーだけが情報にアクセスできます。"
        - open: "ShipItされた記事はインターネット上に公開されます。"
- description (String)
    - チームの説明です
    - Example: "esa.io official documents"
    - 登録がない場合には空文字列(`""`)になります
- icon (String)
    - チームのアイコンです
    - Example: "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png"

MEMO: member_count等の情報を追加予定

## GET /v1/teams

```
GET /v1/teams HTTP/1.1
```

```
{
  "teams": [
    {
      "name": "docs",
      "privacy": "open",
      "description": "esa.io official documents",
      "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
      "url": "https://docs.esa.io/"
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}
```

## GET /v1/teams/:team_name

```
GET /v1/teams/docs HTTP/1.1
```

```
{
  "name": "docs",
  "privacy": "open",
  "description": "esa.io official documents",
  "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
  "url": "https://docs.esa.io/"
}
```

# 統計情報

チームの統計情報を表します。

- members(Integer)
    - チーム内のメンバーの総数です
- posts(Integer)
    - チーム内の記事の総数です
- posts_wip(Integer)
    - チーム内の記事(wip)の総数です
- posts_shipped(Integer)
    - チーム内の記事(shipped)の総数です
- comments(Integer)
    - チーム内の記事につけられたコメントの総数です
- stars(Integer)
    - チーム内の記事につけられたスターの総数です
- daily_active_users(Integer)
    - 過去24時間以内に記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
- weekly_active_users(Integer)
    - 過去7日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
- monthly_active_users(Integer)
    - 過去30日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。

## GET /v1/teams/:team_name/stats

```
GET /v1/teams/foo/stats HTTP/1.1
```

```
{
  "members": 20,
  "posts": 1959,
  "posts_wip": 59,
  "posts_shipped": 1900,
  "comments": 2695,
  "stars": 3115,
  "daily_active_users": 8,
  "weekly_active_users": 14,
  "monthly_active_users": 15
}
```

#  メンバー

チームのメンバーを表します。

- name (String)
    - メンバーの名前です
- screen_name (String)
    - メンバーのスクリーンネームです
- icon (String)
    - メンバーのアイコンのURLです
- email (String)
    - メンバーのemailです
   - このフィールドは **team の owner** だけが取得可能です
- posts_count (Integer)
    - チーム内でメンバーが作成した記事数です

## GET /v1/teams/:team_name/members

```
GET /v1/teams/foo/members HTTP/1.1
```

```
{
  "members": [
    {
      "name": "Atsuo Fukaya",
      "screen_name": "fukayatsu",
      "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
      "email": "fukayatsu@esa.io",
      "posts_count": 222
    },
    {
      "name": "TAEKO AKATSUKA",
      "screen_name": "taea",
      "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg",
      "email": "taea@esa.io",
      "posts_count": 111
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 2,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}
```

# 記事

ユーザが作成した記事を表します。

- number (Integer)
    - チーム内で記事を特定するためのIDです
    - Example: 123
- name (String)
    - 記事名です。タグやカテゴリー部分は含みません。
    - Example: "hi!"
- tags (Array of String)
    - 記事に紐付けられたタグです。
    - Example: ["api", "dev"]
- category (String)
    - 記事が属するカテゴリです
    - Example: "日報/2015/05/09"
    - 設定されていない場合は `null` になります
- full_name (String)
    - カテゴリとタグを含む、記事名です。
    - Example: "日報/2015/05/09/hi! #api #dev"
- wip (Boolean)
    - 記事がWIP(Working In Progress)状態かどうかを表します。
    - Example: "true"
- body_md (String)
    - Markdownで書かれた記事の本文です
    - Example "# Getting Started"
- body_html (String)
    - HTMLに変換された記事の本文です。
    - Example: `<h1>Getting Started</h1>`
- created_at (DateTime String)
    - 記事が作成された日時です
    - Example: "2014-05-10T12:08:55+09:00"
- updated_at (DateTime String)
    - 記事が更新された日時です。
    - Example: "2014-05-11T19:21:00+09:00"
- message (String)
    - 記事更新時の変更メモです。
    - Example: "Add Getting Started section"
- revision_number(Integer)
    - 記事のリビジョン番号です。
    - Example: 47
- created_by (Object)
    - 記事を作成したユーザを表します。
    - name (String)
        - ユーザ名です。
        - Example: "Atsuo Fukaya"
    - screen_name (String)
        - ユーザを一意に識別するIDです。
        - Example: "fukayatsu"
    - icon (String)
        - ユーザのアイコンのURLです。
        - Exmaple: "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
- updated_by (Object)
    - 記事を最後に更新したユーザを表します。フィールドは `created_by` と共通です。
    - name (String)
    - screen_name (String)
    - icon (String)
- kind(String, "stock" or "flow")
    - 記事の種別を表します
- comments_count(Integer)
    - 記事へのコメント数を表します
    - Example: 1
- tasks_count(Integer)
    - 記事中のタスクの総数を表します
    - Example: 1
- done_tasks_count(Integer)
    - 記事中の完了済みのタスクの総数を表します
    - Example: 1
- stargazers_count(Integer)
    - 記事にStarをしている人数を表します
    - Example: 1
- watchers_count(Integer)
    - 記事をWatchしている人数を表します
    - Example: 1
- star(Boolean)
    - ユーザーが記事をStarしているかどうかを表します
    - Example: true
- watch(Boolean)
    - ユーザーが記事をWatchしているかどうかを表します
    - Example: true

## GET /v1/teams/:team_name/posts
記事の一覧を更新日の降順で返却します

```
GET /v1/teams/docs/posts HTTP/1.1
```

### URIクエリ文字列

- q (String)
    - 記事を絞り込むための条件を指定します
    - [#104:  help/記事の検索方法](/posts/104) を参照
- include (String)
    - `comments` を指定するとコメントの配列を含んだレスポンスを返します。
    - `stargazers` を指定するとStarの配列を含んだレスポンスを返します。
    - `stargazers,comments` のように `,` で区切ることで複数指定できます
- sort (String)
    - 設定可能な値
        - `updated` (default)
            - 記事の更新日時
        - `created`
            - 記事の作成日時
        - `stars`
            - 記事へのStarの数
        - `watches`
            - 記事へのWatchの数
        - `comments`
            - 記事へのCommentの数
        - `best_match`
            - 総合的な記事のスコア
- order (String)
    - 設定可能な値
        - `desc` (default)
            - 降順
        - `asc`
            - 昇順

```
{
  "posts": [
    {
      "number": 1,
      "name": "hi!",
      "full_name": "日報/2015/05/09/hi! #api #dev",
      "wip": true,
      "body_md": "# Getting Started",
      "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
      "created_at": "2015-05-09T11:54:50+09:00",
      "message": "Add Getting Started section",
      "url": "https://docs.esa.io/posts/1",
      "updated_at": "2015-05-09T11:54:51+09:00",
      "tags": [
        "api",
        "dev"
      ],
      "category": "日報/2015/05/09",
      "revision_number": 1,
      "created_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      },
      "updated_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}
```

## GET /v1/teams/:team_name/posts/:post_number
指定された投稿を取得します。

### URIクエリ文字列

- include (String)
    - `comments` を指定するとコメントの配列を含んだレスポンスを返します。

```
GET /v1/teams/docs/posts/1 HTTP/1.1
```

```
{
  "number": 1,
  "name": "hi!",
  "full_name": "日報/2015/05/09/hi! #api #dev",
  "wip": true,
  "body_md": "# Getting Started",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T11:54:50+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/1",
  "updated_at": "2015-05-09T11:54:51+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "日報/2015/05/09",
  "revision_number": 1,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "kind": "flow",
  "comments_count": 1,
  "tasks_count": 1,
  "done_tasks_count": 1,
  "stargazers_count": 1,
  "watchers_count": 1,
  "star": true,
  "watch": true
}
```

## POST /v1/teams/:team_name/posts
記事を新たに投稿します。

- post (Object)
    - name (String, **required**)
    - body_md (String)
    - tags (Array of String)
    - category (String)
    - wip (Boolean, default: true)
    - message (String)
    - user(String)
        - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
        - このパラメータは **team の owner** だけ が使用することができます。

```
POST /v1/teams/docs/posts HTTP/1.1
Content-Type: application/json

{
   "post":{
      "name":"hi!",
      "body_md":"# Getting Started\n",
      "tags":[
         "api",
         "dev"
      ],
      "category":"dev/2015/05/10",
      "wip":false,
      "message":"Add Getting Started section"
   }
}
```

```
HTTP/1.1 201 Created
Content-Type: application/json

{
  "number": 5,
  "name": "hi!",
  "full_name": "dev/2015/05/10/hi! #api #dev",
  "wip": false,
  "body_md": "# Getting Started\n",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T12:12:37+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/5",
  "updated_at": "2015-05-09T12:12:37+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "dev/2015/05/10",
  "revision_number": 1,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "kind": "flow",
  "comments_count": 0,
  "tasks_count": 0,
  "done_tasks_count": 0,
  "stargazers_count": 0,
  "watchers_count": 1,
  "star": false,
  "watch": false
}
```

## PATCH /v1/teams/:team_name/posts/:post_number
指定された投稿を編集します。

- post (Object)
    - name (String)
    - body_md (String)
    - tags (Array of String)
    - category (String)
    - wip (Boolean)
    - message (String)
    - created_by(String)
        - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の **作成者** を上書きすることができます。
        - このパラメータは **team の owner** だけ が使用することができます。
    - updated_by(String)
        - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の **更新者** を上書きすることができます。
        - このパラメータは **team の owner** だけ が使用することができます。
    - original_revision (Object)
        - body_md (String)
            - 変更前の記事の本文です
        - number (Integer)
            -  変更前の記事のrevision_numberを指定します
        - user (String)
            - 変更前の記事の最終更新者のscreen_nameを指定します

### original_revisionについて
リクエストに正常な `post.body_md` パラメータと `post.original_revision.*` パラメータが存在する場合、記事更新時に3 way mergeが行われます。original_revisionパラメータが存在しない場合は、変更は常に後勝ちになります。

> [release_note/2014/12/23/記事保存時の自動マージ - docs.esa.io](https://docs.esa.io/posts/35)

```
PATCH /v1/teams/docs/posts/5 HTTP/1.1
Content-Type: application/json

{
   "post":{
      "name":"hi!",
      "body_md":"# Getting Started\n",
      "tags":[
         "api",
         "dev"
      ],
      "category":"dev/2015/05/10",
      "wip":false,
      "message":"Add Getting Started section",
      "original_revision": {
          "body_md": "# Getting ...",
          "number":1,
          "user": "fukayatsu"
        }
    }
}
```

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "number": 5,
  "name": "hi!",
  "full_name": "日報/2015/05/10/hi! #api #dev",
  "wip": false,
  "body_md": "# Getting Started\n",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T12:12:37+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/5",
  "updated_at": "2015-05-09T12:19:48+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "日報/2015/05/10",
  "revision_number": 2,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "overlapped": false,
  "kind": "flow",
  "comments_count": 0,
  "tasks_count": 0,
  "done_tasks_count": 0,
  "stargazers_count": 0,
  "watchers_count": 1,
  "star": false,
  "watch": false
}
```


- overlapped (Boolean)
    - 3 way mergeを行いコンフリクトが起きた場合にのみ `true` になります。

## DELETE /v1/teams/:team_name/posts/:post_number
指定された投稿を削除します。


```
DELETE /v1/teams/docs/posts/5 HTTP/1.1
```

```
HTTP/1.1 204 No Content
```

# コメント
ユーザが作成したコメントを表します。

- id
    - コメントを一意に識別するIDです
    - Example: 123
- body_md (String)
    - Markdownで書かれたコメントの本文です
    -  Example: `LGTM!`
- body_html (String)
    - HTMLに変換されたコメントの本文です
    - Example: `<p>LGTM!</p>`
- created_at (DateTime String)
    - コメントが作成された日時です
    - Example: `2014-05-10T12:45:42+09:00`
- updated_at (DateTime String)
    - コメントが更新された日時です
    - Example: `2014-05-18T23:02:29+09:00`
- url (String)
    - コメントのpermalinkです
    - https://docs.esa.io/posts/2#comment-123
- created_by (Object)
    - コメントを作成したユーザを表します。
    - name (String)
        - ユーザ名です。
        - Example: "Atsuo Fukaya"
    - screen_name (String)
        - ユーザを一意に識別するIDです。
        - Example: "fukayatsu"
    - icon (String)
        - ユーザのアイコンのURLです。
        - Exmaple: "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"

## GET /v1/teams/:team_name/posts/:post_number/comments
記事のコメント一覧を更新日の降順で返却します。

```
GET /v1/teams/docs/posts/2/comments HTTP/1.1
```

```
{
  "comments": [
    {
      "id": 1,
      "body_md": "(大事)",
      "body_html": "<p>(大事)</p>",
      "created_at": "2014-05-10T12:45:42+09:00",
      "updated_at": "2014-05-18T23:02:29+09:00",
      "url": "https://docs.esa.io/posts/2#comment-1",
      "created_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      },
      "stargazers_count": 0,
      "star": false
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}
```

## GET /v1/teams/:team_name/comments/:comment_id
指定されたコメントを取得します。

```
GET /v1/teams/docs/comments/13 HTTP/1.1
```

```
{
  "id": 13,
  "body_md": "読みたい",
  "body_html": "<p>読みたい</p>",
  "created_at": "2014-05-13T16:17:42+09:00",
  "updated_at": "2014-05-18T23:02:29+09:00",
  "url": "https://docs.esa.io/posts/13#comment-13",
  "created_by": {
    "name": "TAEKO AKATSUKA",
    "screen_name": "taea",
    "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg"
  },
  "stargazers_count": 0,
  "star": false
}
```

## POST /v1/teams/:team_name/posts/:post_number/comments
記事に新しいコメントを作成します。

- comment (Object)
    - body_md (String, **required**)
    - user(String)
        - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
        - このパラメータは **team の owner** だけ が使用することができます。

```
POST /v1/teams/docs/posts/2/comments HTTP/1.1
Content-Type: application/json

{"comment":{"body_md":"LGTM!"}}
```

```
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": 22767,
  "body_md": "LGTM!",
  "body_html": "<p>LGTM!</p>\n",
  "created_at": "2015-06-21T19:36:20+09:00",
  "updated_at": "2015-06-21T19:36:20+09:00",
  "url": "https://docs.esa.io/posts/2#comment-22767",
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "stargazers_count": 0,
  "star": false
}
```



## PATCH /v1/teams/:team_name/comments/:comment_id
指定されたコメントを更新します。

- comment (Object)
    - body_md (String)
    - user(String)
        - チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
        - このパラメータは **team の owner** だけ が使用することができます。

```
PATCH /v1/teams/docs/comments/22767 HTTP/1.1
Content-Type: application/json

{"comment":{"body_md":"LGTM!!!"}}
```

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 22767,
  "body_md": "LGTM! :sushi:",
  "body_html": "<p>LGTM!!!</p>\n",
  "created_at": "2015-06-21T19:36:20+09:00",
  "updated_at": "2015-06-21T19:40:33+09:00",
  "url": "https://docs.esa.io/posts/2#comment-22767",
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "stargazers_count": 0,
  "star": false
}
```

## DELETE /v1/teams/:team_name/comments/:comment_id
指定されたコメントを削除します。

```
DELETE /v1/teams/docs/comments/22767 HTTP/1.1
```

```
HTTP/1.1 204 No Content
```

# Star

- created_at (DateTime)
    - Starをした日時です。
- body (null or String)
    - 引用Starの本文です。
- user
    - Starをしたユーザです。

## GET /v1/teams/:team_name/posts/:post_number/stargazers

指定された記事にStarをしたユーザ一覧を取得します。

```
GET /v1/teams/docs/posts/2312/stargazers HTTP/1.1
```

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "stargazers": [
    {
      "created_at": "2016-05-05T11:40:54+09:00",
      "body": null,
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}
```

## POST /v1/teams/:team_name/posts/:post_number/star

指定された記事にStarをします。

- body (String)
    - (任意) 引用Starの本文です。

```
POST /v1/teams/docs/posts/2312/star HTTP/1.1

{"body":"foo bar"}
```

```
HTTP/1.1 204 No Content
```

## DELETE /v1/teams/:team_name/posts/:post_number/star

指定された記事へのStarを取り消します。

```
DELETE /v1/teams/docs/posts/2312/star HTTP/1.1
```

```
HTTP/1.1 204 No Content
```

## GET /v1/teams/:team_name/comments/:comment_id/stargazers

指定されたコメントにStarをしたユーザ一覧を取得します。

```
GET /v1/teams/docs/comments/123/stargazers HTTP/1.1
```

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "stargazers": [
    {
      "created_at": "2016-05-05T11:40:54+09:00",
      "body": null,
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}
```


## POST /v1/teams/:team_name/comments/:comment_id/star

指定されたコメントにStarをします。

- body (String)
    - (任意) 引用Starの本文です。

```
POST /v1/teams/docs/comments/123/star HTTP/1.1

{"body":"foo bar"}
```

```
HTTP/1.1 204 No Content
```

## DELETE /v1/teams/:team_name/comments/:comment_id/star

指定されたコメントへのStarを取り消します。

```
DELETE /v1/teams/docs/comments/123/star HTTP/1.1
```

```
HTTP/1.1 204 No Content
```

# Watch

- created_at (DateTime)
    - Watchをした日時です。
- user
    - Watchをしたユーザです。

## GET /v1/teams/:team_name/posts/:post_number/watchers

指定された記事にWatchをしたユーザ一覧を取得します。

```
GET /v1/teams/docs/posts/2312/watchers HTTP/1.1
```

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "watchers": [
    {
      "created_at": "2016-05-05T11:40:53+09:00",
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}
```

## POST /v1/teams/:team_name/posts/:post_number/watch

指定された記事にWatchをします。

```
POST /v1/teams/docs/posts/2312/watch HTTP/1.1
```

```
HTTP/1.1 204 No Content
```

## DELETE /v1/teams/:team_name/posts/:post_number/watch

指定された記事のWatchを取り消します。

```
DELETE /v1/teams/docs/posts/2312/watch HTTP/1.1
```

```
HTTP/1.1 204 No Content
```

# カテゴリ

## POST v1/teams/:team_name/categories/batch_move

指定されたカテゴリを配下のカテゴリを含めて一括で変更します。

- from (String)
    - 変更元のカテゴリです
- to (String)
    - 変更先のカテゴリです

```
POST /v1/teams/docs/categories/batch_move HTTP/1.1

{
  "from": "/foo/bar/",
  "to": "/baz/"
}

```

```
HTTP/1.1 200 OK

{
  "count": 3,
  "from": "/foo/bar/",
  "to": "/baz/"
}
```

- count:
    - サブカテゴリを含む変更されたカテゴリの数を表します

# 認証中のユーザ

## GET /v1/user

現在のアクセストークンで認証中のユーザの情報を表します。

- id (Integer)
    - サービス内で一意なユーザIDです
- name (String)
    - ユーザの名前です
- screen_name (String)
    - ユーザのスクリーンネームです
- created_at (DateTime String)
    - ユーザの作成日時です
- updated_at (DateTime String)
    - ユーザの更新日時です
- icon (String)
    - ユーザのアイコンのURLです
- email (String)
    - ユーザのemailアドレスです

### URIクエリ文字列

- include (String)
    - `teams` を指定すると所属するチームの配列を含んだレスポンスを返します。

```
GET /v1/user HTTP/1.1
Content-Type: application/json
```

```
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 1,
  "name": "Atsuo Fukaya",
  "screen_name": "fukayatsu",
  "created_at": "2014-05-10T11:50:07+09:00",
  "updated_at": "2016-04-17T12:35:16+09:00",
  "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
  "email": "fukayatsu@esa.io"
}
```

# 他サービスからのインポートスクリプトのサンプル
## Qiita:Team

```import.rb
require 'esa'
require 'json'
require 'pp'
 
class Importer
  def initialize(client, file_path)
    @client = client
    @exported_data = JSON.parse(File.read(file_path))

    return if @exported_data['version'] == 'v1'

    puts "This script is for Qiita Export Format v1. Please contact us (hi@esa.io) (\\( ⁰⊖⁰)/)"
    exit 1
  end
  attr_accessor :client, :exported_data

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true, start_index: 0)
    users_map = {
      "fukayatsu" => 'fukayatsu',
      "ken_c_lo"  => 'taea',
      "foo"       => 'bar',
    }

    exported_data['articles'].sort_by{ |article| article['updated_at'] }.each.with_index do |article, index|
      next unless index >= start_index

      params = {
        name:     article['title'],
        category: "Imports/Qiita",
        tags:     article['tags'].map{ |tag| tag['name'].gsub('/', '-') },
        body_md:  <<-BODY_MD,
created_at: #{article['created_at']}
user: #{article['user']['id']}

#{article['body']}
BODY_MD
        wip:      false,
        message:  '[skip notice] Imported from Qiita',
        user:     users_map[article['user']['id']] || 'esa_bot',  # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        puts "***** index: #{index} *****"
        pp params
        puts
        next
      end

      print "[#{Time.now}] index[#{index}] #{article['title']} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end
 
client = Esa::Client.new(
  access_token: 'xxxxx', 
  current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, '/path/to/exported_from_qiita_team.json')

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true, start_index: 0) 
```

## DocBase

```ruby:import.rb
require 'esa'
require 'pp'

class Importer
  def initialize(client, dir_path)
    @client = client
    @files = Dir.glob File.expand_path(File.join(dir_path, '*.md'))
  end
  attr_accessor :client, :files

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true, start_index: 0)
     files.sort_by { |file| File.basename(file, '.*').to_i }.each.with_index do |file, index|
      next unless index >= start_index

      params = {
        name:     File.basename(file, '.*'),
        category: "Imports/DocBase",
        body_md:  File.read(file),
        wip:      false,
        message:  '[skip notice] Imported from DocBase',
        user:     'esa_bot',  # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        puts "***** index: #{index} *****"
        pp params
        puts
        next
      end

      print "[#{Time.now}] index[#{index}] #{params['name']} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx',
  current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, '/path/to/export_dir') # docbaseからexportしたzipを解答してできたフォルダのpath

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true, start_index: 0)

```

## GitHub Wiki

```ruby:import.rb
require 'esa'
require 'json'
require 'pp'
require 'pry'

class Importer
  def initialize(client, wiki_repo_url)
    @client = client

    `git clone #{wiki_repo_url} wiki_data` unless File.exist? 'wiki_data'
    Dir.chdir('wiki_data')
    @file_names = Dir.glob('*.md')
  end
  attr_accessor :client, :file_names

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true)
    file_names.each do |file_name|

      title = File.basename(file_name)

      params = {
        name:     title,
        category: "Imports/GitHubWiki",
        body_md:  File.read(file_name),
        wip:      false,
        message:  '[skip notice] Imported from GitHub Wiki',
        user:     'esa_bot' # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        pp params
        puts
        next
      end

      print "[#{Time.now}] #{title} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx',
  current_team: 'your-team-name' # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, 'https://github.com/[organization]/[repo].wiki.git')

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true)

```

# 今後の実装予定
- Preview API
- Revision API

はじめに

この記事では esa API v1について説明します。

概要

リクエスト

APIとの通信には HTTPS プロトコルを使用します。アクセス先のホストは api.esa.io です。

パラメータ

API v1へのリクエストには、 GET / POST / PUT / PATCH / DELETE の HTTPメソッドを利用します。
多くのAPIはリクエスト時にパラメータを含められますが、GETリクエストにはURIクエリ文字列を利用し、それ以外の場合にはリクエストボディを利用します。パラメータにはページネーション等の任意で渡すものと、記事名等の必須のものが存在します。

利用制限

現時点では、ユーザ毎に15分間に75リクエストまで受け付けます。

制限状況はAPIのレスポンスヘッダに含まれる X-RateLimit-* の値をご確認下さい。

X-RateLimit-Limit: 75
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1491543000

なお、決められた制限を超える場合は、429 Too Many Requests が返却されます。

ステータスコード

200 / 201 / 204 / 400 / 401 / 402 / 403 / 404 / 500 のステータスコードを利用します。

データ形式

APIとのデータの送受信にはJSONを利用します。JSONをリクエストボディに含める場合はContent-Type: application/jsonをリクエストヘッダに追加して下さい。文字コードはUTF-8を使用して下さい。

エラーレスポンス

エラーが発生した場合は、エラーを表現するオブジェクトを含んだエラーレスポンスが返却されます。

json
{
  "error": "not_found",
  "message": "Not found"
}

ページネーション

リストを返すAPIでは、すべての要素を一度に返すことは現実的ではないため、ページを指定することができるようになっています。これらのAPIには、1から始まるページ番号を表す page パラメータと、1ページあたりに含まれる要素数を表す per_page パラメータを指定することができます。page の初期値は 1 に設定されています。また、 per_page の初期値は 20、最大値は 100 に設定されています。

ページを指定できるAPIでは、ページネーションの情報を含んだレスポンスを返します。

# GET /v1/teams HTTP/1.1

{
    "teams": [
        ...
    ],
    "prev_page": null,
    "next_page": 2,
    "total_count": 30,
    "page": 1,
    "per_page": 20,
    "max_per_page": 100
}
  • prev_page
    • 1つ前のpage番号。存在しない場合はnull
  • next_page
    • 1つ先のpage番号。存在しない場合はnull
  • total_count
    • リソースの総数
  • page
    • 現在のページ番号
  • per_page
    • 1ページあたりに含まれる要素数
  • max_per_page
    • per_pageに指定可能な数の最大値

クライアントライブラリ

現時点で以下のライブラリが利用可能です。

認証と認可

esa APIを利用するには、アクセストークンをリクエストに含める必要があります。アクセストークンは、ユーザの管理画面(https://[team].esa.io/user/tokens)もしくはOAuthを利用した認可フローにより発行できます。

アクセストークン

アクセストークンは、以下のようにリクエストヘッダに含められます。

Authorization: Bearer 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef

また、URIクエリ文字列として指定することも可能です。

api.esa.io/v1/teams?access_token=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef

スコープ

個々のアクセストークンには、複数のスコープを紐付けることができます。アクセストークンが適切なスコープを持っている時のみAPIを利用することができます。esa APIでは以下のスコープを利用することができます。

スコープ 説明
read データの読み出し
write データの書き込み

OAuthを利用した認可フロー

アプリケーションの登録

https://[team].esa.io/user/applications から アプリケーションを登録できます。

※ RailsやSinatraでは、以下の手順は omniauth-esa を使うことで実装の手間を省くことが出来ます。

GET /oauth/authorize

アプリケーションのユーザーに認可画面を表示します。

  • client_id
    • 登録されたアプリケーションのclient_idです
  • redirect_uri
    • 認可が行われた後のリダイレクト先を指定します。アプリケーション登録時に設定したredirect_uriのうちいずれかに一致する必要があります。
    • urn:ietf:wg:oauth:2.0:oobを指定した場合、認可後にリダイレクトせず画面に認証コードを表示します
  • scope
    • アプリケーションが利用するスコープをスペース区切りで指定します
  • response_type
    • code を指定します
  • state
    • CSRF対策のため、認可後にリダイレクトするURLのクエリに含まれる値を指定できます
GET /oauth/authorize?client_id=0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=read+write&state=a7e567e2fb858f0e12838798016ee9cf8ccc778 HTTP/1.1

ss 2016-04-21 14.49.33.png (138.0 kB)

POST /oauth/token

新規にアクセストークンを発行します。

  • client_id
    • 登録されたアプリケーションのClient IDです
  • client_secret
    • 登録されたアプリケーションのClient Secretです
  • grant_type
    • authorization_code を指定します
  • redirect_uri
    • 認可を行った際のredirect_uriを指定します
  • code
    • 認可後のリダイレクト先のURLに含まれた認可コードです
POST /oauth/token HTTP/1.1

{"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","code":"c486e26492dfdc42bcf4e07bfbd84a97f98548c1cf89178a512e76b79b099313","grant_type":"authorization_code","redirect_uri":"urn:ietf:wg:oauth:2.0:oob"}
HTTP/1.1 200 OK

{
  "access_token": "5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc",
  "token_type": "bearer",
  "scope": "read write",
  "created_at": 1461218696
}

GET /oauth/token/info

アクセストークンの情報を取得します

GET /oauth/token/info HTTP/1.1
Authorization: bearer 5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc
HTTP/1.1 200 OK

{"resource_owner_id":1,"scopes":["read","write"],"expires_in_seconds":null,"application":{"uid":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04"},"created_at":1461218696}

POST /oauth/revoke

アクセストークンを失効させます

  • client_id
    • 登録されたアプリケーションのClient IDです
  • client_secret
    • 登録されたアプリケーションのClient Secretです
  • token
    • アクセストークンを指定します
POST /oauth/revoke HTTP/1.1

{"client_id":"0a9bff757caf1eeb344421211637bb6cf724da767062069cafe16a43283a6e04","client_secret":"ca6eb9452f2fdaaa68a8d870bc654db8e6f466f6d42685b914e04c442b2065a2","token":"5d49aab1f0796bbbd78100f06c6cd4d667851644012d37421073bb61126cdafc"}
HTTP/1.1 200 OK

{}

チーム

esa上で所属しているチームを表します。

  • name (String)
    • チームを特定するための一意なIDです。サブドメインになります。
    • Example: "docs"
  • privacy (String)
    • チームの公開範囲です。
      • closed: "チームメンバーだけが情報にアクセスできます。"
      • open: "ShipItされた記事はインターネット上に公開されます。"
  • description (String)
    • チームの説明です
    • Example: "esa.io official documents"
    • 登録がない場合には空文字列("")になります
  • icon (String)

MEMO: member_count等の情報を追加予定

GET /v1/teams

GET /v1/teams HTTP/1.1
{
  "teams": [
    {
      "name": "docs",
      "privacy": "open",
      "description": "esa.io official documents",
      "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
      "url": "https://docs.esa.io/"
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

GET /v1/teams/:team_name

GET /v1/teams/docs HTTP/1.1
{
  "name": "docs",
  "privacy": "open",
  "description": "esa.io official documents",
  "icon": "https://img.esa.io/uploads/production/teams/105/icon/thumb_m_0537ab827c4b0c18b60af6cdd94f239c.png",
  "url": "https://docs.esa.io/"
}

統計情報

チームの統計情報を表します。

  • members(Integer)
    • チーム内のメンバーの総数です
  • posts(Integer)
    • チーム内の記事の総数です
  • posts_wip(Integer)
    • チーム内の記事(wip)の総数です
  • posts_shipped(Integer)
    • チーム内の記事(shipped)の総数です
  • comments(Integer)
    • チーム内の記事につけられたコメントの総数です
  • stars(Integer)
    • チーム内の記事につけられたスターの総数です
  • daily_active_users(Integer)
    • 過去24時間以内に記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
  • weekly_active_users(Integer)
    • 過去7日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。
  • monthly_active_users(Integer)
    • 過去30日間以内にに記事の新規投稿/更新・コメント・Star等の活動を行ったメンバー数です。

GET /v1/teams/:team_name/stats

GET /v1/teams/foo/stats HTTP/1.1
{
  "members": 20,
  "posts": 1959,
  "posts_wip": 59,
  "posts_shipped": 1900,
  "comments": 2695,
  "stars": 3115,
  "daily_active_users": 8,
  "weekly_active_users": 14,
  "monthly_active_users": 15
}

メンバー

チームのメンバーを表します。

  • name (String)
    • メンバーの名前です
  • screen_name (String)
    • メンバーのスクリーンネームです
  • icon (String)
    • メンバーのアイコンのURLです
  • email (String)
    • メンバーのemailです
    • このフィールドは team の owner だけが取得可能です
  • posts_count (Integer)
    • チーム内でメンバーが作成した記事数です

GET /v1/teams/:team_name/members

GET /v1/teams/foo/members HTTP/1.1
{
  "members": [
    {
      "name": "Atsuo Fukaya",
      "screen_name": "fukayatsu",
      "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
      "email": "fukayatsu@esa.io",
      "posts_count": 222
    },
    {
      "name": "TAEKO AKATSUKA",
      "screen_name": "taea",
      "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg",
      "email": "taea@esa.io",
      "posts_count": 111
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 2,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

記事

ユーザが作成した記事を表します。

  • number (Integer)
    • チーム内で記事を特定するためのIDです
    • Example: 123
  • name (String)
    • 記事名です。タグやカテゴリー部分は含みません。
    • Example: "hi!"
  • tags (Array of String)
    • 記事に紐付けられたタグです。
    • Example: ["api", "dev"]
  • category (String)
    • 記事が属するカテゴリです
    • Example: "日報/2015/05/09"
    • 設定されていない場合は null になります
  • full_name (String)
    • カテゴリとタグを含む、記事名です。
    • Example: "日報/2015/05/09/hi! #api #dev"
  • wip (Boolean)
    • 記事がWIP(Working In Progress)状態かどうかを表します。
    • Example: "true"
  • body_md (String)
    • Markdownで書かれた記事の本文です
    • Example "# Getting Started"
  • body_html (String)
    • HTMLに変換された記事の本文です。
    • Example: <h1>Getting Started</h1>
  • created_at (DateTime String)
    • 記事が作成された日時です
    • Example: "2014-05-10T12:08:55+09:00"
  • updated_at (DateTime String)
    • 記事が更新された日時です。
    • Example: "2014-05-11T19:21:00+09:00"
  • message (String)
    • 記事更新時の変更メモです。
    • Example: "Add Getting Started section"
  • revision_number(Integer)
    • 記事のリビジョン番号です。
    • Example: 47
  • created_by (Object)
  • updated_by (Object)
    • 記事を最後に更新したユーザを表します。フィールドは created_by と共通です。
    • name (String)
    • screen_name (String)
    • icon (String)
  • kind(String, "stock" or "flow")
    • 記事の種別を表します
  • comments_count(Integer)
    • 記事へのコメント数を表します
    • Example: 1
  • tasks_count(Integer)
    • 記事中のタスクの総数を表します
    • Example: 1
  • done_tasks_count(Integer)
    • 記事中の完了済みのタスクの総数を表します
    • Example: 1
  • stargazers_count(Integer)
    • 記事にStarをしている人数を表します
    • Example: 1
  • watchers_count(Integer)
    • 記事をWatchしている人数を表します
    • Example: 1
  • star(Boolean)
    • ユーザーが記事をStarしているかどうかを表します
    • Example: true
  • watch(Boolean)
    • ユーザーが記事をWatchしているかどうかを表します
    • Example: true

GET /v1/teams/:team_name/posts

記事の一覧を更新日の降順で返却します

GET /v1/teams/docs/posts HTTP/1.1

URIクエリ文字列

  • q (String)
  • include (String)
    • comments を指定するとコメントの配列を含んだレスポンスを返します。
    • stargazers を指定するとStarの配列を含んだレスポンスを返します。
    • stargazers,comments のように , で区切ることで複数指定できます
  • sort (String)
    • 設定可能な値
      • updated (default)
        • 記事の更新日時
      • created
        • 記事の作成日時
      • stars
        • 記事へのStarの数
      • watches
        • 記事へのWatchの数
      • comments
        • 記事へのCommentの数
      • best_match
        • 総合的な記事のスコア
  • order (String)
    • 設定可能な値
      • desc (default)
        • 降順
      • asc
        • 昇順
{
  "posts": [
    {
      "number": 1,
      "name": "hi!",
      "full_name": "日報/2015/05/09/hi! #api #dev",
      "wip": true,
      "body_md": "# Getting Started",
      "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
      "created_at": "2015-05-09T11:54:50+09:00",
      "message": "Add Getting Started section",
      "url": "https://docs.esa.io/posts/1",
      "updated_at": "2015-05-09T11:54:51+09:00",
      "tags": [
        "api",
        "dev"
      ],
      "category": "日報/2015/05/09",
      "revision_number": 1,
      "created_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      },
      "updated_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

GET /v1/teams/:team_name/posts/:post_number

指定された投稿を取得します。

URIクエリ文字列

  • include (String)
    • comments を指定するとコメントの配列を含んだレスポンスを返します。
GET /v1/teams/docs/posts/1 HTTP/1.1
{
  "number": 1,
  "name": "hi!",
  "full_name": "日報/2015/05/09/hi! #api #dev",
  "wip": true,
  "body_md": "# Getting Started",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T11:54:50+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/1",
  "updated_at": "2015-05-09T11:54:51+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "日報/2015/05/09",
  "revision_number": 1,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "kind": "flow",
  "comments_count": 1,
  "tasks_count": 1,
  "done_tasks_count": 1,
  "stargazers_count": 1,
  "watchers_count": 1,
  "star": true,
  "watch": true
}

POST /v1/teams/:team_name/posts

記事を新たに投稿します。

  • post (Object)
    • name (String, required)
    • body_md (String)
    • tags (Array of String)
    • category (String)
    • wip (Boolean, default: true)
    • message (String)
    • user(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
POST /v1/teams/docs/posts HTTP/1.1
Content-Type: application/json

{
   "post":{
      "name":"hi!",
      "body_md":"# Getting Started\n",
      "tags":[
         "api",
         "dev"
      ],
      "category":"dev/2015/05/10",
      "wip":false,
      "message":"Add Getting Started section"
   }
}
HTTP/1.1 201 Created
Content-Type: application/json

{
  "number": 5,
  "name": "hi!",
  "full_name": "dev/2015/05/10/hi! #api #dev",
  "wip": false,
  "body_md": "# Getting Started\n",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T12:12:37+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/5",
  "updated_at": "2015-05-09T12:12:37+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "dev/2015/05/10",
  "revision_number": 1,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "kind": "flow",
  "comments_count": 0,
  "tasks_count": 0,
  "done_tasks_count": 0,
  "stargazers_count": 0,
  "watchers_count": 1,
  "star": false,
  "watch": false
}

PATCH /v1/teams/:team_name/posts/:post_number

指定された投稿を編集します。

  • post (Object)
    • name (String)
    • body_md (String)
    • tags (Array of String)
    • category (String)
    • wip (Boolean)
    • message (String)
    • created_by(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の 作成者 を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
    • updated_by(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の 更新者 を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
    • original_revision (Object)
      • body_md (String)
        • 変更前の記事の本文です
      • number (Integer)
        • 変更前の記事のrevision_numberを指定します
      • user (String)
        • 変更前の記事の最終更新者のscreen_nameを指定します

original_revisionについて

リクエストに正常な post.body_md パラメータと post.original_revision.* パラメータが存在する場合、記事更新時に3 way mergeが行われます。original_revisionパラメータが存在しない場合は、変更は常に後勝ちになります。

release_note/2014/12/23/記事保存時の自動マージ - docs.esa.io

PATCH /v1/teams/docs/posts/5 HTTP/1.1
Content-Type: application/json

{
   "post":{
      "name":"hi!",
      "body_md":"# Getting Started\n",
      "tags":[
         "api",
         "dev"
      ],
      "category":"dev/2015/05/10",
      "wip":false,
      "message":"Add Getting Started section",
      "original_revision": {
          "body_md": "# Getting ...",
          "number":1,
          "user": "fukayatsu"
        }
    }
}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "number": 5,
  "name": "hi!",
  "full_name": "日報/2015/05/10/hi! #api #dev",
  "wip": false,
  "body_md": "# Getting Started\n",
  "body_html": "<h1 id=\"1-0-0\" name=\"1-0-0\">\n<a class=\"anchor\" href=\"#1-0-0\"><i class=\"fa fa-link\"></i><span class=\"hidden\" data-text=\"Getting Started\"> &gt; Getting Started</span></a>Getting Started</h1>\n",
  "created_at": "2015-05-09T12:12:37+09:00",
  "message": "Add Getting Started section",
  "url": "https://docs.esa.io/posts/5",
  "updated_at": "2015-05-09T12:19:48+09:00",
  "tags": [
    "api",
    "dev"
  ],
  "category": "日報/2015/05/10",
  "revision_number": 2,
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "updated_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "http://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "overlapped": false,
  "kind": "flow",
  "comments_count": 0,
  "tasks_count": 0,
  "done_tasks_count": 0,
  "stargazers_count": 0,
  "watchers_count": 1,
  "star": false,
  "watch": false
}
  • overlapped (Boolean)
    • 3 way mergeを行いコンフリクトが起きた場合にのみ true になります。

DELETE /v1/teams/:team_name/posts/:post_number

指定された投稿を削除します。

DELETE /v1/teams/docs/posts/5 HTTP/1.1
HTTP/1.1 204 No Content

コメント

ユーザが作成したコメントを表します。

  • id
    • コメントを一意に識別するIDです
    • Example: 123
  • body_md (String)
    • Markdownで書かれたコメントの本文です
    • Example: LGTM!
  • body_html (String)
    • HTMLに変換されたコメントの本文です
    • Example: <p>LGTM!</p>
  • created_at (DateTime String)
    • コメントが作成された日時です
    • Example: 2014-05-10T12:45:42+09:00
  • updated_at (DateTime String)
    • コメントが更新された日時です
    • Example: 2014-05-18T23:02:29+09:00
  • url (String)
  • created_by (Object)

GET /v1/teams/:team_name/posts/:post_number/comments

記事のコメント一覧を更新日の降順で返却します。

GET /v1/teams/docs/posts/2/comments HTTP/1.1
{
  "comments": [
    {
      "id": 1,
      "body_md": "(大事)",
      "body_html": "<p>(大事)</p>",
      "created_at": "2014-05-10T12:45:42+09:00",
      "updated_at": "2014-05-18T23:02:29+09:00",
      "url": "https://docs.esa.io/posts/2#comment-1",
      "created_by": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      },
      "stargazers_count": 0,
      "star": false
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

GET /v1/teams/:team_name/comments/:comment_id

指定されたコメントを取得します。

GET /v1/teams/docs/comments/13 HTTP/1.1
{
  "id": 13,
  "body_md": "読みたい",
  "body_html": "<p>読みたい</p>",
  "created_at": "2014-05-13T16:17:42+09:00",
  "updated_at": "2014-05-18T23:02:29+09:00",
  "url": "https://docs.esa.io/posts/13#comment-13",
  "created_by": {
    "name": "TAEKO AKATSUKA",
    "screen_name": "taea",
    "icon": "https://img.esa.io/uploads/production/users/2/icon/thumb_m_2690997f07b7de3014a36d90827603d6.jpg"
  },
  "stargazers_count": 0,
  "star": false
}

POST /v1/teams/:team_name/posts/:post_number/comments

記事に新しいコメントを作成します。

  • comment (Object)
    • body_md (String, required)
    • user(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
POST /v1/teams/docs/posts/2/comments HTTP/1.1
Content-Type: application/json

{"comment":{"body_md":"LGTM!"}}
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": 22767,
  "body_md": "LGTM!",
  "body_html": "<p>LGTM!</p>\n",
  "created_at": "2015-06-21T19:36:20+09:00",
  "updated_at": "2015-06-21T19:36:20+09:00",
  "url": "https://docs.esa.io/posts/2#comment-22767",
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "stargazers_count": 0,
  "star": false
}

PATCH /v1/teams/:team_name/comments/:comment_id

指定されたコメントを更新します。

  • comment (Object)
    • body_md (String)
    • user(String)
      • チームメンバーのscreen_nameもしくは "esa_bot" を指定することで記事の投稿者を上書きすることができます。
      • このパラメータは team の owner だけ が使用することができます。
PATCH /v1/teams/docs/comments/22767 HTTP/1.1
Content-Type: application/json

{"comment":{"body_md":"LGTM!!!"}}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 22767,
  "body_md": "LGTM! :sushi:",
  "body_html": "<p>LGTM!!!</p>\n",
  "created_at": "2015-06-21T19:36:20+09:00",
  "updated_at": "2015-06-21T19:40:33+09:00",
  "url": "https://docs.esa.io/posts/2#comment-22767",
  "created_by": {
    "name": "Atsuo Fukaya",
    "screen_name": "fukayatsu",
    "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
  },
  "stargazers_count": 0,
  "star": false
}

DELETE /v1/teams/:team_name/comments/:comment_id

指定されたコメントを削除します。

DELETE /v1/teams/docs/comments/22767 HTTP/1.1
HTTP/1.1 204 No Content

Star

  • created_at (DateTime)
    • Starをした日時です。
  • body (null or String)
    • 引用Starの本文です。
  • user
    • Starをしたユーザです。

GET /v1/teams/:team_name/posts/:post_number/stargazers

指定された記事にStarをしたユーザ一覧を取得します。

GET /v1/teams/docs/posts/2312/stargazers HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{
  "stargazers": [
    {
      "created_at": "2016-05-05T11:40:54+09:00",
      "body": null,
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

POST /v1/teams/:team_name/posts/:post_number/star

指定された記事にStarをします。

  • body (String)
    • (任意) 引用Starの本文です。
POST /v1/teams/docs/posts/2312/star HTTP/1.1

{"body":"foo bar"}
HTTP/1.1 204 No Content

DELETE /v1/teams/:team_name/posts/:post_number/star

指定された記事へのStarを取り消します。

DELETE /v1/teams/docs/posts/2312/star HTTP/1.1
HTTP/1.1 204 No Content

GET /v1/teams/:team_name/comments/:comment_id/stargazers

指定されたコメントにStarをしたユーザ一覧を取得します。

GET /v1/teams/docs/comments/123/stargazers HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{
  "stargazers": [
    {
      "created_at": "2016-05-05T11:40:54+09:00",
      "body": null,
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

POST /v1/teams/:team_name/comments/:comment_id/star

指定されたコメントにStarをします。

  • body (String)
    • (任意) 引用Starの本文です。
POST /v1/teams/docs/comments/123/star HTTP/1.1

{"body":"foo bar"}
HTTP/1.1 204 No Content

DELETE /v1/teams/:team_name/comments/:comment_id/star

指定されたコメントへのStarを取り消します。

DELETE /v1/teams/docs/comments/123/star HTTP/1.1
HTTP/1.1 204 No Content

Watch

  • created_at (DateTime)
    • Watchをした日時です。
  • user
    • Watchをしたユーザです。

GET /v1/teams/:team_name/posts/:post_number/watchers

指定された記事にWatchをしたユーザ一覧を取得します。

GET /v1/teams/docs/posts/2312/watchers HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{
  "watchers": [
    {
      "created_at": "2016-05-05T11:40:53+09:00",
      "user": {
        "name": "Atsuo Fukaya",
        "screen_name": "fukayatsu",
        "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png"
      }
    }
  ],
  "prev_page": null,
  "next_page": null,
  "total_count": 1,
  "page": 1,
  "per_page": 20,
  "max_per_page": 100
}

POST /v1/teams/:team_name/posts/:post_number/watch

指定された記事にWatchをします。

POST /v1/teams/docs/posts/2312/watch HTTP/1.1
HTTP/1.1 204 No Content

DELETE /v1/teams/:team_name/posts/:post_number/watch

指定された記事のWatchを取り消します。

DELETE /v1/teams/docs/posts/2312/watch HTTP/1.1
HTTP/1.1 204 No Content

カテゴリ

POST v1/teams/:team_name/categories/batch_move

指定されたカテゴリを配下のカテゴリを含めて一括で変更します。

  • from (String)
    • 変更元のカテゴリです
  • to (String)
    • 変更先のカテゴリです
POST /v1/teams/docs/categories/batch_move HTTP/1.1

{
  "from": "/foo/bar/",
  "to": "/baz/"
}

HTTP/1.1 200 OK

{
  "count": 3,
  "from": "/foo/bar/",
  "to": "/baz/"
}
  • count:
    • サブカテゴリを含む変更されたカテゴリの数を表します

認証中のユーザ

GET /v1/user

現在のアクセストークンで認証中のユーザの情報を表します。

  • id (Integer)
    • サービス内で一意なユーザIDです
  • name (String)
    • ユーザの名前です
  • screen_name (String)
    • ユーザのスクリーンネームです
  • created_at (DateTime String)
    • ユーザの作成日時です
  • updated_at (DateTime String)
    • ユーザの更新日時です
  • icon (String)
    • ユーザのアイコンのURLです
  • email (String)
    • ユーザのemailアドレスです

URIクエリ文字列

  • include (String)
    • teams を指定すると所属するチームの配列を含んだレスポンスを返します。
GET /v1/user HTTP/1.1
Content-Type: application/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 1,
  "name": "Atsuo Fukaya",
  "screen_name": "fukayatsu",
  "created_at": "2014-05-10T11:50:07+09:00",
  "updated_at": "2016-04-17T12:35:16+09:00",
  "icon": "https://img.esa.io/uploads/production/users/1/icon/thumb_m_402685a258cf2a33c1d6c13a89adec92.png",
  "email": "fukayatsu@esa.io"
}

他サービスからのインポートスクリプトのサンプル

Qiita:Team

import.rb
require 'esa'
require 'json'
require 'pp'

class Importer
  def initialize(client, file_path)
    @client = client
    @exported_data = JSON.parse(File.read(file_path))

    return if @exported_data['version'] == 'v1'

    puts "This script is for Qiita Export Format v1. Please contact us (hi@esa.io) (\\( ⁰⊖⁰)/)"
    exit 1
  end
  attr_accessor :client, :exported_data

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true, start_index: 0)
    users_map = {
      "fukayatsu" => 'fukayatsu',
      "ken_c_lo"  => 'taea',
      "foo"       => 'bar',
    }

    exported_data['articles'].sort_by{ |article| article['updated_at'] }.each.with_index do |article, index|
      next unless index >= start_index

      params = {
        name:     article['title'],
        category: "Imports/Qiita",
        tags:     article['tags'].map{ |tag| tag['name'].gsub('/', '-') },
        body_md:  <<-BODY_MD,
created_at: #{article['created_at']}
user: #{article['user']['id']}

#{article['body']}
BODY_MD
        wip:      false,
        message:  '[skip notice] Imported from Qiita',
        user:     users_map[article['user']['id']] || 'esa_bot',  # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        puts "***** index: #{index} *****"
        pp params
        puts
        next
      end

      print "[#{Time.now}] index[#{index}] #{article['title']} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx', 
  current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, '/path/to/exported_from_qiita_team.json')

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true, start_index: 0) 

DocBase

import.rb
require 'esa'
require 'pp'

class Importer
  def initialize(client, dir_path)
    @client = client
    @files = Dir.glob File.expand_path(File.join(dir_path, '*.md'))
  end
  attr_accessor :client, :files

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true, start_index: 0)
     files.sort_by { |file| File.basename(file, '.*').to_i }.each.with_index do |file, index|
      next unless index >= start_index

      params = {
        name:     File.basename(file, '.*'),
        category: "Imports/DocBase",
        body_md:  File.read(file),
        wip:      false,
        message:  '[skip notice] Imported from DocBase',
        user:     'esa_bot',  # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        puts "***** index: #{index} *****"
        pp params
        puts
        next
      end

      print "[#{Time.now}] index[#{index}] #{params['name']} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx',
  current_team: 'your-team-name', # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, '/path/to/export_dir') # docbaseからexportしたzipを解答してできたフォルダのpath

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true, start_index: 0)

GitHub Wiki

import.rb
require 'esa'
require 'json'
require 'pp'
require 'pry'

class Importer
  def initialize(client, wiki_repo_url)
    @client = client

    `git clone #{wiki_repo_url} wiki_data` unless File.exist? 'wiki_data'
    Dir.chdir('wiki_data')
    @file_names = Dir.glob('*.md')
  end
  attr_accessor :client, :file_names

  def wait_for(seconds)
    (seconds / 10).times do
      print '.'
      sleep 10
    end
    puts
  end

  def import(dry_run: true)
    file_names.each do |file_name|

      title = File.basename(file_name)

      params = {
        name:     title,
        category: "Imports/GitHubWiki",
        body_md:  File.read(file_name),
        wip:      false,
        message:  '[skip notice] Imported from GitHub Wiki',
        user:     'esa_bot' # 記事作成者上書き: owner権限が必要
      }

      if dry_run
        pp params
        puts
        next
      end

      print "[#{Time.now}] #{title} => "
      response = client.create_post(params)
      case response.status
      when 201
        puts "created: #{response.body["full_name"]}"
      when 429
        retry_after = (response.headers['Retry-After'] || 20 * 60).to_i
        puts "rate limit exceeded: will retry after #{retry_after} seconds."
        wait_for(retry_after)
        redo
      else
        puts "failure with status: #{response.status}"
        exit 1
      end
    end
  end
end

client = Esa::Client.new(
  access_token: 'xxxxx',
  current_team: 'your-team-name' # 移行先のチーム名(サブドメイン)
)
importer = Importer.new(client, 'https://github.com/[organization]/[repo].wiki.git')

# dry_run: trueで確認後に dry_run: falseで実際にimportを実行
importer.import(dry_run: true)

今後の実装予定

  • Preview API
  • Revision API