API
エラー
1 つの形、2 つのレベル、そしてすべてに付くリクエスト id。
エンベロープ
type は分岐に使える固定された集合で、今後増えることはありません。code は具体的で追加されていくものなので、知らない code はその type として扱ってください。requestId は成功も含めたすべてのレスポンスに付き、報告とログ行を結び付けるものです。
{ "error": { "type": "validation_error", "code": "invalid_email_address", "message": "Not a valid email address: ada@", "param": "to.0", "docUrl": "https://openemail.uk/docs/api/errors#invalid_email_address", "requestId": "req_e103790543af4…" }}param はドット区切りで添字も付くため、それを含むフィールドではなく、まさにその要素(to.0、attachments.2.filename)を指します。
ステータスコード
| ステータス | type | 主なコード |
|---|---|---|
| 400 | invalid_request_error | malformed_json, invalid_idempotency_key |
| 401 | authentication_error | missing_api_key, invalid_api_key, revoked_api_key, invalid_credential_type |
| 403 | permission_error | insufficient_scope, from_address_forbidden |
| 404 | not_found_error | resource_not_found |
| 409 | conflict_error | email_not_cancellable, translation_not_configured |
| 422 | validation_error | invalid_email_address, reserved_header, too_many_recipients, unknown_parameter, idempotency_key_reuse, label_not_directly_settable, unknown_language, translation_too_long |
| 429 | rate_limit_error | send_quota_exceeded, too_many_inboxes |
| 500 | api_error | internal_error |
| 503 | api_error | translation_failed |
404 は「存在しない」と「別のワークスペースに属している」を決して区別しません。これは意図的です。その違い自体が情報だからです。
429 が Retry-After を伴うことはないので、待ち時間は自分で決めてください。send_quota_exceeded は月間の送信枠で、毎月 1 日にリセットされます。バックオフするのではなく、人に見せてください。too_many_inboxes は使い捨て受信トレイの発行上限で、すでに持っている受信トレイの延長はこの上限を消費しません。
500 と 503 は type を共有しますが、呼び出し側にとっての意味は異なります。503 は応答しなかった依存先(現時点では翻訳サービス)であり、リクエストはそのまま再試行する価値があります。500 は当方側の問題で、requestId を添えて報告する価値があります。
エラーを処理する
挙動の分岐には type を使い、人に見せるメッセージには code を読んでください。知らない code はクライアント側のエラーではありません。以前より細かく失敗に名前を付けた、という意味です。
const res = await fetch(`${BASE}/emails`, { method: 'POST', headers, body }); if (!res.ok) { const { error } = await res.json(); switch (error.type) { case 'rate_limit_error': throw new RateLimited(error.code); case 'validation_error': // error.param points at the offending field throw new BadRequest(`${error.param}: ${error.message}`); case 'authentication_error': // revoked_api_key and expired_api_key are worth telling an operator apart throw new AuthFailed(error.code); default: // quote requestId when you report it throw new Unexpected(error.message, error.requestId); }}