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

エラーハンドリング

Re:port Flow の PDF 生成・ダウンロード系エンドポイントのエラーは、statusCode / code / message / error / details / docsUrl を持つ JSON で返ります(OAuth のトークンエンドポイントだけは RFC 6749 形式です。OAuth 2.0 認証を参照)。プログラムで分岐するときは message の文言ではなく code を使ってください。429 と 409(FILE_NOT_READY)以外の 4xx はリクエスト側の問題なので、内容を直さない限り再送しても失敗します(例外として、非同期生成の直後に状態 API・ダウンロードが返す 404 は、生成中のことがあるので 1〜2 回確かめ直します)。5xx のうち 500 / 502 / 503 は指数バックオフでリトライする価値があり(同期生成の 504 / 524 は、生成が続いていて二重に計上されうるので再送しない。それ以外の 5xx も、リクエストがサーバーに届いた後のことがあるので自動では再送しない)、429 と 409 はサーバーが返す Retry-After の秒数だけ待てばそのまま再送できます。このガイドでは、ステータスコード別の原因と、リトライ・タイムアウト・ロギングの実装例をまとめます。

エラーレスポンスはどんな形式?​

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

{
"statusCode": 403,
"code": "PLAN_PAGE_LIMIT_EXCEEDED",
"message": "Monthly page limit reached (30 pages). Upgrade your plan or wait until next month.",
"error": "Forbidden",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#plan_page_limit_exceeded"
}
フィールド説明
statusCodeHTTP ステータスコード
code機械判定用のエラーコード(例: PLAN_PAGE_LIMIT_EXCEEDED)。分岐にはこれを使う
message人が読むための説明。既定は英語、Accept-Language: ja で日本語。バリデーションエラーだけは文字列の配列
errorHTTP ステータスの理由句(Bad Request 等)
details項目ごとの詳細(field / expected / code / message)。無ければ空配列
docsUrlそのコードの説明ページ(エラーコード一覧)への URL
retryAfter429 と 409 FILE_NOT_READY のときだけ。再試行まで待つ秒数(Retry-After ヘッダーと同じ値)

コードの一覧、言語の切り替え、message が配列になる条件は エラーコード一覧 にまとめています。

HTTPステータスコード一覧​

2xx 成功​

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

4xx クライアントエラー​

400 Bad Request​

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

{
"statusCode": 400,
"code": "VALIDATION_FAILED",
"message": [
"version must be an integer number"
],
"error": "Bad Request",
"details": [
{ "field": "version", "message": "version must be an integer number" }
],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#validation_failed"
}

対処法:

  • details[].field が指す項目を確認
  • 型の確認(integer 等)
  • 必須フィールドの確認

例:パラメータの型の不一致(PARAMS_TYPE_MISMATCH)

params の値がテンプレートの型と合わないときは、details にキーと期待する型が入ります。

{
"statusCode": 400,
"code": "PARAMS_TYPE_MISMATCH",
"message": "Parameter validation failed: \"amount\" must be a number",
"error": "Bad Request",
"details": [
{
"field": "params.amount",
"expected": "number",
"code": "PARAMS_TYPE_MISMATCH",
"message": "\"amount\" must be a number"
}
],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#params_type_mismatch"
}

キーは details[].field(params.<キー>)、期待する型は details[].expected(string / number / date / boolean / object / array)で判定してください。

ファイル名の禁止文字(FILE_NAME_INVALID)・重複(FILE_NAME_DUPLICATED)なども 400 で返ります。コードごとの対処は エラーコード一覧 を参照してください。

401 Unauthorized​

原因: 認証情報が不正

{
"statusCode": 401,
"code": "INVALID_CREDENTIALS",
"message": "Invalid API key or access token.",
"error": "Unauthorized",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#invalid_credentials"
}

対処法:

  • アプリケーションキー(appkey ヘッダー)またはアクセストークンを確認
  • キーの再生成状況、トークンの有効期限・スコープ(pdf:generate)を確認

403 Forbidden​

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

{
"statusCode": 403,
"code": "PLAN_PAGE_LIMIT_EXCEEDED",
"message": "Monthly page limit reached (30 pages). Upgrade your plan or wait until next month.",
"error": "Forbidden",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#plan_page_limit_exceeded"
}

対処法:

  • PLAN_PAGE_LIMIT_EXCEEDED: 月間の PDF 出力ページ数の上限に達した。プランをアップグレードするか翌月まで待つ(上限は出力回数ではなく出力ページ数の合計で数える)
  • PLAN_NOT_FOUND: ワークスペースのプラン情報を確認(管理画面の ワークスペース設定 → プラン)
  • WORKSPACE_ROLE_DENIED: OAuth で認可したユーザーにワークスペースでの権限が無い。管理者にロールを確認してもらう
  • 別のワークスペースの requestId をダウンロード・状態確認しても 403 にはならず、存在を知らせないため 404 になる(下記)

404 Not Found​

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

{
"statusCode": 404,
"code": "DESIGN_VERSION_NOT_FOUND",
"message": "The specified design and version do not exist.",
"error": "Not Found",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#design_version_not_found"
}

対処法:

  • PDF 生成で designId または version が存在しない場合は DESIGN_VERSION_NOT_FOUND になる。designId と version の組み合わせを確認する(テンプレート ID は 0eUDdgAjNXrrItA2 のような16文字の英数字。形式チェックは行わないため、誤った ID も 400 ではなくこの 404 になる)
  • パラメータ取得で version を指定しない場合だけ、存在しないテンプレートは DESIGN_NOT_FOUND になる(version を指定した場合は DESIGN_VERSION_NOT_FOUND)
  • ダウンロードの 404(FILE_NOT_FOUND)は、ID の誤り、別のワークスペースの ID、失敗から 24 時間を過ぎた単一生成、または出力が削除されたことを意味する。生成中は通常 409 FILE_NOT_READY(ジョブの記録を保存できなかったリクエストは生成中でも 404)、生成失敗は 422 FILE_GENERATION_FAILED が返る

409 Conflict​

原因: ダウンロード対象のファイルがまだ生成中(FILE_NOT_READY)

{
"statusCode": 409,
"code": "FILE_NOT_READY",
"message": "The file is still being generated. Retry after 2 seconds or poll GET /v1/file/status/kZ3mP9qL2xV7nR4t.",
"error": "Conflict",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#file_not_ready",
"retryAfter": 2
}

対処法:

  • Retry-After ヘッダー(秒)だけ待って再送する
  • 完了待ちには 生成状態の取得 を使う

412 Precondition Failed​

原因: 認証ヘッダー(appkey と Authorization のどちらも)が無い、または appkey ヘッダーの形式が不正

{
"statusCode": 412,
"code": "AUTH_HEADER_MISSING",
"message": "Authentication header is missing. Send an `appkey` header or `Authorization: Bearer <token>`.",
"error": "Precondition Failed",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#auth_header_missing"
}

対処法:

  • appkey ヘッダー、または Authorization: Bearer <token> ヘッダーが設定されているか確認
なぜ 401 ではなく 412 なのか

認証ヘッダーが無いときのステータスは、既存クライアントとの互換性のため従来どおり 412 のままにしています(401 には変更していません)。区別には code(AUTH_HEADER_MISSING)を使ってください。

413 Payload Too Large​

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

このときの応答本文の形は、このページで保証しているエラー形式と異なる場合があります。ステータスコード 413 で判定してください。

対処法:

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

422 Unprocessable Entity​

原因: ダウンロード対象のリクエストの生成が失敗している(FILE_GENERATION_FAILED)

対処法:

  • 生成状態の取得 の error.code で失敗理由を確認する
  • 原因(パラメータの型など)を直してから、新しいリクエストとして生成し直す

429 Too Many Requests​

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

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Rate limit exceeded for this workspace. Please try again in 60 seconds.",
"error": "Too Many Requests",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#rate_limited",
"retryAfter": 60
}

Retry-After ヘッダ: RFC 9110 §10.2.3 に準拠し、再試行まで待つべき秒数を整数で返します。本文の retryAfter も同じ値です。クライアントはエラーメッセージをパースする代わりにこの値だけ待てば十分です。

対処法:

  • Retry-After ヘッダの秒数だけ待ってから再送する
  • 同期エンドポイント: 30 req/min、非同期エンドポイント: 100 req/min、状態 API: 300 req/min
  • バッチ処理の場合は非同期エンドポイントを使用
  • 詳細は制限事項を参照

5xx サーバーエラー​

500 Internal Server Error​

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

{
"statusCode": 500,
"code": "INTERNAL_ERROR",
"message": "Internal Server Error",
"error": "Internal Server Error",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#internal_error"
}

5xx の message は内部情報を出さない汎用の文言です。

対処法:

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

503 Service Unavailable​

原因: PDF 生成の待ち行列が満杯(RENDER_QUEUE_FULL)など、一時的に処理できない

対処法:

  • 時間をおいてリトライする

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

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

async function generatePDF(params) {
try {
const response = await axios.post(url, data, config);
return response.data;
} catch (error) {
if (error.response) {
// サーバーがエラーレスポンスを返した。投げ直すエラーにも元の応答を付けておく
// (下のリトライ処理が status・code・Retry-After を読むため)
try {
handleApiError(error.response);
} catch (apiError) {
apiError.response = error.response;
throw apiError;
}
} else if (error.request) {
// レスポンスがない(接続できなかった、またはタイムアウトした)
console.error('ネットワークエラー:', error.message);
// 元の code(ECONNREFUSED / ECONNABORTED など)を残す。下のリトライ判定が使う
const networkError = new Error('サーバーに接続できません');
networkError.code = error.code;
throw networkError;
} else {
// リクエスト設定時のエラー
console.error('リクエストエラー:', error.message);
throw error;
}
}
}

// PDF / ZIP を responseType: 'arraybuffer' で受け取ると、エラー本文もバイナリで届く。
// JSON に戻してから code を読む(413 / 504 / 524 などは JSON でないことがあるので、そのときは null)
function errorBody(data) {
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
try {
return JSON.parse(new TextDecoder().decode(data));
} catch {
return null;
}
}
return data && typeof data === 'object' ? data : null;
}

function handleApiError(response) {
const { status } = response;
const data = errorBody(response.data) ?? {};

// 文言ではなく code で分岐する(message は言語設定で変わる)
switch (data.code) {
case 'VALIDATION_FAILED':
case 'PARAMS_TYPE_MISMATCH':
console.error('バリデーションエラー:', data.details);
throw new Error(`リクエストが不正です: ${data.details.map(d => d.field).join(', ')}`);

case 'AUTH_HEADER_MISSING':
case 'INVALID_CREDENTIALS':
throw new Error('APIキー(またはアクセストークン)を確認してください');

case 'PLAN_PAGE_LIMIT_EXCEEDED':
throw new Error('今月の出力ページ数の上限に達しました');

case 'DESIGN_NOT_FOUND':
case 'DESIGN_VERSION_NOT_FOUND':
throw new Error('指定されたテンプレートまたはバージョンが存在しません');

default:
if (status === 504 || status === 524) {
// 生成が続いていることがあり、再送すると二重に生成・計上されうる
throw new Error('タイムアウトしました。生成が続いている可能性があるので再送せず、長くかかる生成は非同期エンドポイントを使ってください');
}
if ([500, 502, 503].includes(status)) {
throw new Error('一時的なエラーです。しばらく待ってから再試行してください');
}
console.error('予期しないエラー:', data);
throw new Error(`エラー (${status} ${data.code}): ${data.message}`);
}
}

2. リトライロジック​

一時的なエラー(500・502・503・429・409 FILE_NOT_READY)に対してリトライを実装します。同期生成の 504 / 524 やクライアント側のタイムアウトは、サーバー側で生成が続いていて二重に生成・計上されうるので再送しません。Retry-After がある場合はその秒数を優先します。

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;
}

// Retry-After(秒)があれば優先、無ければ指数バックオフ
const retryAfter = Number(error.response?.headers?.['retry-after']);
const delay = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: Math.min(initialDelay * Math.pow(backoffFactor, attempt), maxDelay);

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

throw lastError;
}

// 接続できなかった(リクエストがサーバーに届いていない)ことが確かなエラー
const NOT_SENT_CODES = new Set(['ECONNREFUSED', 'ENOTFOUND', 'EAI_AGAIN']);

function isRetryableError(error) {
if (!error.response) {
// 接続前のエラーだけ再送する。タイムアウト(ECONNABORTED / ETIMEDOUT)は、
// サーバー側で生成が進んでいてページ数が計上されることがあるので再送しない
return NOT_SENT_CODES.has(error.code);
}

const { status } = error.response;

// 500・502・503・429・生成中(409 FILE_NOT_READY)だけリトライ。本文がバイナリでも判定できるよう status で見る。
// 504 / 524(経路のタイムアウト)は生成が続いていることがあり、再送すると二重に生成・計上されうるので
// 再送しない。長くかかる生成は非同期エンドポイントを使う
return [500, 502, 503, 429, 409].includes(status);
}

3. タイムアウト処理​

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

4. エラーロギング​

function logError(error, context) {
// 本文がバイナリでも code を読めるよう errorBody で戻す(JSON でない文字列はそのまま残す)
const raw = error.response?.data;
const body = errorBody(raw) ?? (typeof raw === 'string' ? raw : null);
const errorLog = {
timestamp: new Date().toISOString(),
context,
error: {
message: error.message,
stack: error.stack,
response: error.response ? {
status: error.response.status,
code: body?.code,
data: body
} : 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
import urllib3
from typing import Optional, Dict, Any

class ReportFlowError(Exception):
"""Re:port Flow API エラーの基底クラス"""
def __init__(self, status_code: int, code: str, message, details=None):
self.status_code = status_code
self.code = code
self.message = message # バリデーションエラーのときはリスト
self.details = details or []
super().__init__(f"{code} ({status_code}): {message}")

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

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

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

class RateLimitError(ReportFlowError):
"""レート制限 (429)"""
def __init__(self, status_code, code, message, details=None, retry_after=None):
super().__init__(status_code, code, message, details)
self.retry_after = retry_after

class ServerError(ReportFlowError):
"""サーバーエラー (500 / 502 / 503)"""
pass

class SyncTimeoutError(ReportFlowError):
"""同期生成のタイムアウト(504 / 524 / クライアント側)。
サーバー側で生成が続いていて二重に生成・計上されうるので再送しない。長い処理は非同期を使う"""
pass

class ConnectionLostError(SyncTimeoutError):
"""送信後に接続が切れた可能性がある(結果が分からない)。同じ理由で再送しない"""
pass

def generate_pdf_with_retry(params: Dict[str, Any], max_retries: int = 3) -> bytes:
"""リトライ付きPDF生成(再送するのは 429 と 500 / 502 / 503 だけ)"""
for attempt in range(max_retries):
try:
return generate_pdf(params)
except RateLimitError as e:
if attempt == max_retries - 1:
raise
time.sleep(e.retry_after or 60)
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:
# それ以外(SyncTimeoutError を含む)はリトライしない
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=70)
response.raise_for_status()
return response.content
except requests.exceptions.HTTPError as e:
handle_http_error(e.response)
except requests.exceptions.ConnectTimeout:
# 接続が確立する前のタイムアウト。リクエストは届いていないので再送してよい
raise ServerError(503, 'CONNECT_TIMEOUT', '接続できませんでした')
except requests.exceptions.Timeout:
raise SyncTimeoutError(504, 'TIMEOUT', 'リクエストがタイムアウトしました')
except requests.exceptions.ConnectionError as e:
reason = getattr(e.args[0], 'reason', None) if e.args else None
if isinstance(reason, urllib3.exceptions.NewConnectionError):
# 接続拒否・名前解決の失敗(NameResolutionError を含む)。リクエストは届いていないので再送してよい
raise ServerError(503, 'CONNECT_FAILED', str(e))
# 送信後に切れた可能性がある(RemoteDisconnected など)ので再送しない
raise ConnectionLostError(0, 'CONNECTION_LOST', str(e))
except requests.exceptions.RequestException as e:
# 受信中の切断(ChunkedEncodingError など)も結果が分からないので再送しない
raise ConnectionLostError(0, 'CONNECTION_LOST', str(e))

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

if status == 400:
raise ValidationError(status, code, message, details)
elif status in (401, 412):
raise AuthenticationError(status, code, message, details)
elif status == 404:
raise NotFoundError(status, code, message, details)
elif status == 429:
retry_after = response.headers.get('Retry-After')
raise RateLimitError(status, code, message, details,
int(retry_after) if retry_after else None)
elif status in (504, 524):
raise SyncTimeoutError(status, code, message, details)
elif status in (500, 502, 503):
raise ServerError(status, code, message, details)
else:
raise ReportFlowError(status, code, message, details)

# 使用例
try:
pdf_data = generate_pdf_with_retry({
'app_key': 'your-app-key',
'design_id': '0eUDdgAjNXrrItA2',
'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.details}")
except AuthenticationError as e:
print(f"認証エラー: APIキーを確認してください ({e.code})")
except NotFoundError as e:
print(f"リソースが見つかりません: {e.code}")
except ServerError as e:
print(f"サーバーエラー: しばらく待ってから再試行してください")
except ReportFlowError as e:
print(f"エラー: {e}")

トラブルシューティング​

よくあるエラーと対処法​

code原因対処法
DESIGN_VERSION_NOT_FOUNDPDF 生成・パラメータ取得(version 指定あり)でテンプレート ID またはバージョンが存在しないテンプレート ID(0eUDdgAjNXrrItA2 のような16文字の英数字)を管理画面からコピーし直し、version を確認する
DESIGN_NOT_FOUNDパラメータ取得(version 指定なし)でテンプレート ID が存在しないテンプレート ID を管理画面からコピーし直す
FILE_NOT_FOUNDダウンロードの ID の誤り・別のワークスペースの ID・失敗から 24 時間を過ぎた単一生成・出力の削除生成 API のレスポンスの値と、生成時と同じワークスペースの認証情報を使う
FILE_NAME_INVALIDファイル名に / \ : * ? " < > | または制御文字を含むこれらの文字を除去または置換
INVALID_CREDENTIALS認証失敗アプリケーションキー(またはアクセストークン)を確認
AUTH_HEADER_MISSING認証ヘッダー欠落リクエストに appkey: <キー> または Authorization: Bearer <token> ヘッダーを追加
FILE_NOT_READY生成中のファイルをダウンロードしようとしたRetry-After 秒待つか、生成状態の取得 で完了を待つ

すべてのコードは エラーコード一覧 を参照してください。

次のステップ​