WebAPIでエラーをどう表現すべき？15のサービスを調査してみた

**2017-01-05 追記**

2016年3月にエラーの標準形式RFC7807「Problem Details for HTTP APIs」が提案され、今日現在proposed standard(標準化への提唱)となっています。こちらも是非ご覧ください。

* [RFC 7807 - Problem Details for HTTP APIs](https://tools.ietf.org/html/rfc7807)
* [HTTP APIの詳細なエラー情報をレスポンスに持たせるための仕様](https://www.eisbahn.jp/yoichiro/2017/01/rfc_7807.html)

-------------

最近はREST APIを提供しているサービスが増えてきていますね！また公開されるAPIだけでなく、[Microservices]なアーキテクチャを採用して、バックエンドがWeb APIで通信するケースも増えてきているように思います。

APIを使うときはあまり気にしたこともなかったですが、いざAPIを設計してみるとどんなインターフェイスがいいのか、どんな形式がいいのかといった疑問が次々と出てきます。

今回、僕の会社で開発しているテスト自動化サービスの[ShouldBee]のチームでも、Web APIの設計に関して、**エラーをどのように表現したらいいか**がトピックにあがりました。

そこで、他のサービスではどのように表現しているか参考にしたいと思い、今回調査してみることしました。今回、調査は @t-hiroyoshi に協力してもらいました。ありがとうございます！

## 調査したサービス

今回、Web APIについて調査したウェブサービスは、次の15のサービスです。ぱっと思いついた順番で調べていったので、特に順番には意味はありません。また、調査範囲も思いつきです。

1. GitHub
2. Facebook
3. Heroku
4. Toggl
5. Yahoo!
6. GREE
7. Nike
8. Twitter
9. Google
10. Twilio
11. Foursquare
12. Flickr
13. Linkedin
14. Philips Hue
15. Qiita

それでは、それぞれのサービスがWeb APIでエラーをJSONでどう表現しているか、個々に見て行きましょう。

### GitHub

[GitHub Developer](https://developer.github.com/v3/#client-errors)

```json
{
   "message": "Validation Failed",
   "errors": [
     {
       "resource": "Issue",
       "field": "title",
       "code": "missing_field"
     }
   ]
 }
```

* `message`: エラーメッセージ
* `errors`: 送られたフィールドにエラーがあった場合に追加されます。
    * `resource`: 対象のリソース
    * `field`: 対象のフィールド
    * `code`: エラーコード

典型的なエラーは`message`だけなのでシンプルですが、`errors`で複数エラーが返せるのと、詳細なエラーを表現することもできる構造になっています。

### Facebook

[Using the Graph API](https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#errors)

```json
     {
       "error": {
         "message": "Message describing the error", 
         "type": "OAuthException", 
         "code": 190,
         "error_subcode": 460,
         "error_user_title": "A title",
         "error_user_msg": "A message"
       }
     }
```

* `message`: エラーメッセージ
* `type`: エラーの型
* `code`: エラーコード
* `error_subcode`: エラーのサブコード
* `error_user_title`: ユーザに直接表示できるメッセージで、翻訳もされるとのことです。ダイアログのタイトルなどに表示するという用途。
* `error_user_msg`: これもユーザに直接表示できるメッセージで、翻訳もされるとのことです。

Facebookの特徴は、エンドユーザ向けメッセージが別についてくることです。しかもリクエストのロケールにあわせて翻訳までしてくれるので、アプリを作る側にとったら便利ですね。

### Heroku

[Platform API Reference | Heroku Dev Center](https://devcenter.heroku.com/articles/platform-api-reference#errors)

```json
{
  "id":       "rate_limit",
  "message":  "Your account reached the API rate limit\nPlease wait a few minutes before making new requests",
  "url":      "https://devcenter.heroku.com/articles/platform-api-reference#rate-limits"
}
```

* `id`: エラーコード
* `message`: エラーメッセージ
* `url`: エラーの詳細が記述されたページのURL

Herokuは極めてシンプルな形式ですが、エラーについての説明があるページのURLがついてくるのが特徴のひとつです。

### Toggl

[toggl_api_docs/reports.md at master · toggl/toggl_api_docs](https://github.com/toggl/toggl_api_docs/blob/master/reports.md#failed-requests)

```json
  {
    "error": {
      "message":"We are sorry, this Error should never happen to you",
      "tip":"Please contact support@toggl.com with information on your request",
      "code":500
    }
  }
```

* `message`: エラーメッセージ
* `tip`: このエラーをどう対処したらいいかの説明
* `code`: HTTPステータスコード

Togglの特徴は、エラーをどう対処したらいいか簡単なヒントが付いてくる点です。

### Yahoo!

[Yahoo!デベロッパーネットワーク:エラーメッセージおよびコード - Yahoo!デベロッパーネットワーク](http://developer.yahoo.co.jp/appendix/errors.html)

```json
{
  "Error" : {
    "Message" : "error message"
  }
}
```

* `Message`: エラーメッセージ

Yahoo!のAPIではエラーメッセージだけを返すようになっているようです。

### GREE

[Error Codes - GREE Developer Center](https://docs.developer.gree.net/ja/globaltechnicalspecs/api/errorcodes)

```json
{
   "code":1001,
   "message":"Message API (batch type) to same user is called 
             several times by official user in a certain period of time. Service unavailable.",
   "ref_url":"http:\/\/docs.developer.gree.net\/error.html"
}
```

* `code`: エラーコード
* `message`: エラーメッセージ
* `ref_url`: エラーの詳細が記述されたページのURL

GREEのエラーレスポンスも、Herokuと同様にシンプルな形式ですが、エラーについての説明があるページのURLがついてくるのが特徴のひとつです。

### Nike

[API Error Codes](https://developer.nike.com/documentation/api-docs/error-messages/error-codes-and-responses.html)

```json
{
    "requestId": "-1712857370761229397",
    "errors": [
        {
            "code": 90,
            "message": "Invalid format: Start Date must follow format yyyy-mm-dd"
        },
        {
            "code": 100,
            "message": "Invalid format: End Date must follow format yyyy-mm-dd"
        },
        {
            "code": 110,
            "message": "Invalid Format: count must be greater than or equal to 1"
        }
    ]
}
```

* `requestId`: リクエストID
* `code`: エラーコード
* `message`: エラーメッセージ

Nikeでは、リクエストIDがありこれは開発者がAPIプロバイダに問い合わせしたときに利用されるものと思われます。また、複数のエラーが返せるようになっているのも特徴です。

### Twitter

[Error Codes & Responses | Twitter Developers](https://dev.twitter.com/overview/api/response-codes)

```json
{
  "errors": [
    {
      "message": "Sorry, that page does not exist",
      "code": 34
    }
  ]
}
```

* `message`: エラーメッセージ
* `code`: エラーコード

TwitterのAPIのエラーレスポンスは、エラーが配列になっていてエラーが複数返せるように設計されている点が特徴です。

### Google 

[Standard Error Responses - DoubleClick Search API — Google Developers](https://developers.google.com/doubleclick-search/v2/standard-error-responses)

```json
{
  "error": {
    "errors": [
      {
        "domain": "global",
        "reason": "appNotConfigured",
        "message": "The app with id {appId} does not exist or is not properly configured as a Google Drive app."
      }
    ],
    "code": 403,
    "message": "The app with id {appId} does not exist or is not properly configured as a Google Drive app."
  }
}
```

* `domain`: どの領域のエラーかを特定するためのもののようです。例えばYouTube領域固有のエラーであれば`youtube.parameter`などとなるようです。
* `reason`: エラーコード
* `message`: エラーメッセージ
* `code`: HTTPステータスコード

Googleの特徴は、様々なサービスを横断して統一された形式のJSONを使っている点です。そのためサービスを表すために`domain`フィールドが設けられています。また、複数のエラーを返せるように`errors`フィールドは配列になっています。


### Twilio 

[Twilio Cloud Communications - APIs for Voice, VoIP, and Text Messaging](https://www.twilio.com/docs/api/rest/response#response-formats-exceptions)

```json
{
    "status": 400,
    "message": "No to number is specified",
    "code": 21201,
    "more_info": "http:\/\/www.twilio.com\/docs\/errors\/21201"
}
```

* `status`: HTTPステータスコード
* `message`: エラーメッセージ
* `code` エラーコード
* `more_info`: エラーについて詳細が書いてあるページのリンク

`code`と`more_info`はフィールド自体がない場合もあるとのことです。


### Foursquare

[Responses & Errors](https://developer.foursquare.com/overview/responses)

```json
{
    "meta": {
        "code": 400,
        "errorDetail": "Missing access credentials. See https://developer.foursquare.com/docs/oauth.html for details.",
        "errorType": "invalid_auth"
    },
    "response": {}
}
```

* `code`: HTTPステータスコード
* `errorDetail`: エラーメッセージ 
* `errorType`: エラーコード

Foursquareの特徴は、通常のレスポンスとエラーレスポンスで形式の違いがなく、エラーが発生したときはレスポンスの`meta`キーにエラーが入ることです。

### Flickr

[Flickr Services](https://www.flickr.com/services/api/response.json.html)

```json
{
    "code": 96,
    "message": "Invalid signature",
    "stat": "fail"
}
```

* `code`: エラーコード
* `message`: エラーメッセージ
* `stat`: そのリクエストが成功したか失敗したか

Flickrはシンプルな形式ですが、`stat`があるのが特徴です。

### Linkedin

```json
{
    "errorCode": 0,
    "message": "Unknown authentication scheme",
    "requestId": "PFB5T8NVLO",
    "status": 401,
    "timestamp": 1432283172233
}
```

* `errorCode`: エラーコード
* `message`: エラーメッセージ
* `requestId`: リクエストID。
* `status`: HTTPステータスコード。
* `timestamp`: リクエストのタイムスタンプ。

LinkedInのエラーレスポンスは、リクエストIDとリクエストのタイムスタンプがあるのが特徴です。もし開発者から問い合わせがあったときにコミュニケーションしやすそうですね。ただ、リクエストIDなどはヘッダにあったほうがいいのかもとも思います。

### Philips Hue

[API Core concepts | Philips Hue API](http://www.developers.meethue.com/documentation/api-core-concepts#_Toc259440465)

```json
[
  {
    "error": {
      "type": 2,
      "address": "/",
      "description": "body contains invalid json"
    }
  }
]
```

* `type`: エラーコード
* `address`: エラーがあったURL
* `description`: エラーメッセージ

### Qiita

[Qiita API v2ドキュメント - Qiita:Developer - Qiita](https://qiita.com/api/v2/docs#%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9)

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

* `message`: エラーメッセージ
* `type`: エラーコード

## まとめ

### エラー内容の比較

以上のサービスのAPIが提供している情報を表にまとめるとこのようになります。

<img width="484" alt="APIのエラー形式のサービス比較表_-_Google_スプレッドシート.png" src="https://qiita-image-store.s3.amazonaws.com/0/889/cf5402f6-32f7-d354-8f99-68aa791abbd2.png">


今回調査した範囲では全てのサービスが、人が読んで理解できる形式のメッセージを必ずエラーに含めていました。

エラーコードはプログラムがそのエラーをハンドルしやすいように、intまたは英数字からなるstringでした。なお、エラーコードを返すAPIの中には、エラーコード一覧表を提供しているサービスとそうでないサービスがありました。クライアントとしてはエラーコードの一覧表があれば、適切なエラーハンドリングをプログラミングしておくことができるので、APIプロバイダが一覧表を公開しておくと良いと思います。

サービスの性質によっては複数エラーを返せたほうがいいかもしれません。クライアントがアプリで入力欄が複数あるUIでは、一度に複数のエラーを把握できたほうが、いいユーザ体験になることがあります。


### フィールド名の比較

それぞれのサービスで使われているフィールド名をまとめたのが次の表です。

<img width="480" alt="APIのエラー形式のサービス比較表_-_Google_スプレッドシート.png" src="https://qiita-image-store.s3.amazonaws.com/0/889/e4a490a5-1135-55bc-2580-afec99d1f50e.png">

メッセージについては、多くのサービスが`message`を採用していました。エラーコードについては、`code`が最も多く、`type`と名付ける例もありました。複数エラーを返す可能性があるサービスでは、`errors`と複数形のフィールド名になっています。ステータスコードについては、`code`ないしは`status`が使われていました。詳細URLを提供しているサービスは、今回の調査範囲では少数派でしたが、`url`, `rel_url`, `more_info`が使われていました。

APIを設計するときは、他のサービスで使われているネーミングに倣っておくと、他のAPIの経験がある開発者とって、そのフィールドが何を表現しているのか理解しやすくなるのではないでしょうか？

最後に調査していて、APIドキュメントに、エラーレスポンスのボディについて説明を設けているサービスとそうでないサービスがあることに気づきました。開発者としては、APIがどのような形式のエラーを返すのかが分かりやすく書いてあると、クライアントを実装しやすいと思います。また、ドキュメントにはエラーに含まれるフィールド名のリストや表だけではなく、具体的にJSONの例もあると親切だと感じます。

## エラーレスポンスで考慮したいこと

以上の調査を終えて、僕なりにエラーに表現するときに考慮したいことをまとめてみます。

* **エラーメッセージ**: 開発者が読んで分かるメッセージを入れる。
* **エラーコード**: クライアントのプログラムがエラーハンドリングの手がかりになる情報を与える。加えて、ドキュメントではエラーコードの一覧を公開する。
* **複数エラー**: サービスによってケースバイケースだが、複数のエラーが表現できるようになっている。
* **詳細URL**: 公開APIでドキュメントが整っているなら、開発者のググる手間がなくなる。
* **一貫性**: どのエンドポイントでも同じ構造のJSONになっている。
* **シンプル**: 複雑な構造がなくネストが浅いJSONのほうがクライアントが複雑な型を定義しなくて済む。

## おわり

Web APIでエラーをどのように表現したらいいか、15のサービスの例を見ながら考えてみました。どのサービスでも共通する部分がある一方、独自の工夫がこらされているAPIもありました。Web APIを実装する機会があればぜひ参考にしてみてください。

## 関連シリーズ

* [ウェブサービスの請求書の出し方どうすべき？12サービスの請求書を集めて比較してみた](http://craftsman-software.com/blog/2015-10-22-saas-invoice/)

[ShouldBee]: https://shouldbee.at/
[Microservices]: http://martinfowler.com/articles/microservices.html

---

最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:→[Twitter@suin](https://twitter.com/suin)