メインコンテンツまでスキップ

エラーハンドリング

Report Flow APIのエラーハンドリング方法とベストプラクティスを説明します。

エラーレスポンスの形式

すべてのエラーは以下の形式で返されます:

{
"statusCode": 400,
"message": "エラーの詳細説明",
"error": "エラータイプ"
}

HTTPステータスコード一覧

2xx 成功

コード意味説明
200 OK成功リクエストが正常に処理されました
202 Accepted受付非同期処理が受け付けられました

4xx クライアントエラー

400 Bad Request

原因: リクエストの形式や内容が不正

{
"statusCode": 400,
"message": [
"designId must be a UUID",
"version must be an integer number"
],
"error": "Bad Request"
}

対処法:

  • リクエストボディのバリデーション
  • 型の確認(UUID、integer等)
  • 必須フィールドの確認

例:ファイル名バリデーションエラー

{
"statusCode": 400,
"message": "ファイル名に使用できない文字が含まれています(/ \\ : * ? \" < > | および制御文字は使用不可)",
"error": "Bad Request"
}

401 Unauthorized

原因: 認証情報が不正または欠落

{
"statusCode": 401,
"message": "Invalid credentials",
"error": "Unauthorized"
}

対処法:

  • アプリケーションキー(appkey ヘッダー)を確認
  • キーの有効期限・再生成状況を確認

404 Not Found

原因: リソースが見つからない

{
"statusCode": 404,
"message": "ジョブが見つかりません",
"error": "Not Found"
}

対処法:

  • リソースIDを確認
  • リソースが削除されていないか確認

403 Forbidden

原因: プラン制限超過またはアクセス権限不足

{
"statusCode": 403,
"message": "Plan limit exceeded",
"error": "Forbidden"
}

対処法:

  • ワークスペースのプラン制限を確認(デザインファイル数、月間生成回数)
  • 管理画面でプランのアップグレードを検討

412 Precondition Failed

原因: 認証ヘッダーが不足

{
"statusCode": 412,
"message": "認証ヘッダーが不足しています",
"error": "Precondition Failed"
}

対処法:

  • appkey ヘッダーが設定されているか確認

413 Payload Too Large

原因: リクエストサイズが50MBを超過

{
"statusCode": 413,
"message": "Payload Too Large",
"error": "Request Entity Too Large"
}

対処法:

  • 画像を最適化する(解像度・圧縮率の調整)
  • リクエストを分割する
  • 詳細は制限事項を参照

429 Too Many Requests

原因: Rate Limit超過(Workspace単位で適用)

{
"statusCode": 429,
"message": "Rate limit exceeded for this workspace. Please try again in XX seconds.",
"error": "Too Many Requests"
}

対処法:

  • リクエスト間隔を空ける
  • 同期エンドポイント: 30 req/min、非同期エンドポイント: 100 req/min
  • バッチ処理の場合は非同期エンドポイントを使用
  • 詳細は制限事項を参照

5xx サーバーエラー

500 Internal Server Error

原因: サーバー内部エラー

{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}

対処法:

  • リトライ処理を実装
  • 継続する場合はサポートに連絡

エラーハンドリングのベストプラクティス

1. 適切なエラーキャッチ

async function generatePDF(params) {
try {
const response = await axios.post(url, data, config);
return response.data;
} catch (error) {
if (error.response) {
// サーバーがエラーレスポンスを返した
handleApiError(error.response);
} else if (error.request) {
// リクエストは送信されたがレスポンスがない
console.error('ネットワークエラー:', error.message);
throw new Error('サーバーに接続できません');
} else {
// リクエスト設定時のエラー
console.error('リクエストエラー:', error.message);
throw error;
}
}
}

function handleApiError(response) {
const { status, data } = response;

switch (status) {
case 400:
console.error('バリデーションエラー:', data.message);
throw new Error(`リクエストが不正です: ${data.message}`);

case 401:
console.error('認証エラー');
throw new Error('APIキーを確認してください');

case 404:
console.error('リソースが見つかりません');
throw new Error('指定されたリソースが存在しません');

case 500:
console.error('サーバーエラー');
throw new Error('一時的なエラーです。しばらく待ってから再試行してください');

default:
console.error('予期しないエラー:', data);
throw new Error(`エラー (${status}): ${data.message}`);
}
}

2. リトライロジック

一時的なサーバーエラー(5xx)に対してリトライを実装します。

async function generatePDFWithRetry(params, options = {}) {
const {
maxRetries = 3,
initialDelay = 1000,
maxDelay = 10000,
backoffFactor = 2
} = options;

let lastError;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await generatePDF(params);
} catch (error) {
lastError = error;

// リトライ可能なエラーか判定
if (!isRetryableError(error)) {
throw error;
}

// 最後の試行ではリトライしない
if (attempt === maxRetries - 1) {
break;
}

// 指数バックオフで待機
const delay = Math.min(
initialDelay * Math.pow(backoffFactor, attempt),
maxDelay
);

console.log(`リトライ ${attempt + 1}/${maxRetries} (${delay}ms後)`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}

throw lastError;
}

function isRetryableError(error) {
if (!error.response) {
// ネットワークエラーはリトライ可能
return true;
}

const { status } = error.response;

// 5xxエラーのみリトライ
return status >= 500 && status < 600;
}

3. タイムアウト処理

async function generatePDFWithTimeout(params, timeout = 30000) {
return Promise.race([
generatePDF(params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('タイムアウト')), timeout)
)
]);
}

4. エラーロギング

function logError(error, context) {
const errorLog = {
timestamp: new Date().toISOString(),
context,
error: {
message: error.message,
stack: error.stack,
response: error.response ? {
status: error.response.status,
data: error.response.data
} : null
}
};

console.error(JSON.stringify(errorLog));

// エラートラッキングサービスに送信(例: Sentry)
// Sentry.captureException(error, { contexts: { custom: context } });
}

// 使用例
try {
await generatePDF(params);
} catch (error) {
logError(error, {
operation: 'generatePDF',
designId: params.designId,
fileName: params.fileName
});
throw error;
}

Python実装例

import requests
import time
from typing import Optional, Dict, Any

class ReportFlowError(Exception):
"""Report Flow API エラーの基底クラス"""
def __init__(self, status_code: int, message: str, error: str):
self.status_code = status_code
self.message = message
self.error = error
super().__init__(f"{error} ({status_code}): {message}")

class ValidationError(ReportFlowError):
"""バリデーションエラー (400)"""
pass

class AuthenticationError(ReportFlowError):
"""認証エラー (401)"""
pass

class NotFoundError(ReportFlowError):
"""リソースが見つからない (404)"""
pass

class ServerError(ReportFlowError):
"""サーバーエラー (5xx)"""
pass

def generate_pdf_with_retry(params: Dict[str, Any], max_retries: int = 3) -> bytes:
"""リトライ付きPDF生成"""
for attempt in range(max_retries):
try:
return generate_pdf(params)
except ServerError as e:
if attempt == max_retries - 1:
raise

delay = 1000 * (2 ** attempt) # 指数バックオフ
time.sleep(delay / 1000)
print(f"リトライ {attempt + 1}/{max_retries}")
except ReportFlowError:
# サーバーエラー以外はリトライしない
raise

def generate_pdf(params: Dict[str, Any]) -> bytes:
"""PDF生成"""
url = "https://api.re-port-flow.com/v1/file/sync/single"
headers = {
'appkey': params['app_key'],
'Content-Type': 'application/json'
}
data = {
'designId': params['design_id'],
'version': params['version'],
'content': {
'fileName': params['file_name'],
'params': params['data']
}
}

try:
response = requests.post(url, headers=headers, json=data, timeout=30)
response.raise_for_status()
return response.content
except requests.exceptions.HTTPError as e:
handle_http_error(e.response)
except requests.exceptions.Timeout:
raise ServerError(504, 'リクエストがタイムアウトしました', 'Gateway Timeout')
except requests.exceptions.RequestException as e:
raise ServerError(500, str(e), 'Network Error')

def handle_http_error(response):
"""HTTPエラーを適切な例外に変換"""
try:
error_data = response.json()
status = error_data.get('statusCode', response.status_code)
message = error_data.get('message', 'Unknown error')
error = error_data.get('error', 'Error')
except ValueError:
status = response.status_code
message = response.text
error = 'Unknown Error'

if status == 400:
raise ValidationError(status, message, error)
elif status == 401:
raise AuthenticationError(status, message, error)
elif status == 404:
raise NotFoundError(status, message, error)
elif 500 <= status < 600:
raise ServerError(status, message, error)
else:
raise ReportFlowError(status, message, error)

# 使用例
try:
pdf_data = generate_pdf_with_retry({
'app_key': 'your-app-key',
'design_id': '660e8400-e29b-41d4-a716-446655440000',
'version': 1,
'file_name': 'invoice.pdf',
'data': {
'customerName': '山田太郎',
'invoiceNumber': 'INV-2024-001'
}
})

with open('invoice.pdf', 'wb') as f:
f.write(pdf_data)

except ValidationError as e:
print(f"バリデーションエラー: {e.message}")
except AuthenticationError as e:
print(f"認証エラー: APIキーを確認してください")
except NotFoundError as e:
print(f"リソースが見つかりません: {e.message}")
except ServerError as e:
print(f"サーバーエラー: しばらく待ってから再試行してください")
except ReportFlowError as e:
print(f"エラー: {e}")

トラブルシューティング

よくあるエラーと対処法

エラー原因対処法
designId must be a UUIDデザインIDの形式が不正UUID形式(例: 550e8400-e29b-41d4-a716-446655440000)を確認
ファイル名に使用できない文字が含まれていますファイル名に / \ : * ? " < > | または制御文字を含むこれらの文字を除去または置換
Invalid credentials認証失敗アプリケーションキーを確認

次のステップ