Error Handling
How to handle errors from the Report Flow API and recommended patterns for production clients.
Error response shape
All errors are returned in the following form:
{
"statusCode": 400,
"message": "Detailed error description",
"error": "Error type"
}
HTTP status codes
2xx Success
| Code | Meaning | Notes |
|---|---|---|
| 200 OK | Success | The request was processed normally |
| 202 Accepted | Accepted | Asynchronous processing was accepted |
4xx Client errors
400 Bad Request
Cause: The request format or content is invalid.
{
"statusCode": 400,
"message": [
"designId must be a UUID",
"version must be an integer number"
],
"error": "Bad Request"
}
What to do:
- Validate the request body
- Check field types (UUID, integer, …)
- Confirm required fields are present
Example: file name validation error
{
"statusCode": 400,
"message": "ファイル名に使用できない文字が含まれています(/ \\ : * ? \" < > | および制御文字は使用不可)",
"error": "Bad Request"
}
The server message is currently in Japanese. Treat the disallowed characters as / \ : * ? " < > | plus control characters; everything else (including spaces, parentheses, hash, full-width characters) is allowed.
401 Unauthorized
Cause: Authentication is missing or invalid.
{
"statusCode": 401,
"message": "認証情報が不正です",
"error": "Unauthorized"
}
(The server returns the message in Japanese; it translates to "Invalid credentials".)
What to do:
- Verify the
appkeyheader value - Check whether the key was rotated/regenerated
404 Not Found
Cause: The requested resource does not exist.
{
"statusCode": 404,
"message": "指定したデザインが存在しません",
"error": "Not Found"
}
(The server returns the message in Japanese; it translates to "The specified design does not exist". When a specific version is missing, the message is 指定したバージョン(X)が存在しません ("The specified version X does not exist").)
What to do:
- Verify the
designId - Check whether the requested version exists
403 Forbidden
Cause: Plan limit exceeded or insufficient permissions.
{
"statusCode": 403,
"message": "プラン情報が見つからないため、この操作は許可されていません",
"error": "Forbidden"
}
(The server returns the message in Japanese; it translates to "Plan information was not found, so this operation is not permitted". When the monthly file generation limit is exceeded, the message is 今月のファイル作成回数上限(X)に達しました。プランをアップグレードするか、翌月までお待ちください。 ("This month's file creation limit (X) has been reached. Please upgrade the plan or wait until next month.").)
What to do:
- Verify the workspace plan information (admin console: Workspace settings → Plan)
- If the monthly file creation limit is exceeded, upgrade the plan or wait until next month
412 Precondition Failed
Cause: Required authentication header is missing.
{
"statusCode": 412,
"message": "認証方式ヘッダーが存在しません",
"error": "Precondition Failed"
}
(The server returns the message in Japanese; it translates to "Authentication header is missing".)
What to do:
- Confirm the
appkeyheader is set on the request
413 Payload Too Large
Cause: Request body exceeds 50MB.
{
"statusCode": 413,
"message": "Payload Too Large",
"error": "Request Entity Too Large"
}
What to do:
- Optimise images (resolution / compression)
- Split the request
- See Limitations
429 Too Many Requests
Cause: Rate limit exceeded (applied per workspace).
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"statusCode": 429,
"message": "Rate limit exceeded for this workspace. Please try again in 60 seconds.",
"error": "Too Many Requests"
}
Retry-After header: Per RFC 9110 §10.2.3, the integer number of seconds to wait before retrying. Clients should rely on this header rather than parsing the message body. The number embedded in the message body is the same value in human-readable form.
What to do:
- Wait for the duration in
Retry-Afterand re-send - Sync endpoints: 30 req/min; async endpoints: 100 req/min
- Use the async endpoints for batch processing
- See Limitations
5xx Server errors
500 Internal Server Error
Cause: Internal server error.
{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}
What to do:
- Implement retry with backoff
- If it persists, contact support
Recommended client patterns
1. Catching errors
async function generatePDF(params) {
try {
const response = await axios.post(url, data, config);
return response.data;
} catch (error) {
if (error.response) {
// The server returned an error response
handleApiError(error.response);
} else if (error.request) {
// The request was sent but no response was received
console.error('Network error:', error.message);
throw new Error('Could not reach the server');
} else {
// Error while configuring the request
console.error('Request error:', error.message);
throw error;
}
}
}
function handleApiError(response) {
const { status, data } = response;
switch (status) {
case 400:
console.error('Validation error:', data.message);
throw new Error(`Invalid request: ${data.message}`);
case 401:
console.error('Authentication error');
throw new Error('Check your appkey');
case 404:
console.error('Resource not found');
throw new Error('The requested resource does not exist');
case 500:
console.error('Server error');
throw new Error('Temporary error. Please retry shortly.');
default:
console.error('Unexpected error:', data);
throw new Error(`Error (${status}): ${data.message}`);
}
}
2. Retry with exponential backoff
Retry on transient server errors (5xx). For 429 specifically, prefer the Retry-After header value.
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;
}
// For 429, honour Retry-After if present
const retryAfterSec = Number(error.response?.headers?.['retry-after']);
const delay = Number.isFinite(retryAfterSec)
? retryAfterSec * 1000
: Math.min(
initialDelay * Math.pow(backoffFactor, attempt),
maxDelay,
);
console.log(`Retry ${attempt + 1}/${maxRetries} (after ${delay}ms)`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw lastError;
}
function isRetryableError(error) {
if (!error.response) {
// Network errors: retryable
return true;
}
const { status } = error.response;
// 429 + 5xx are retryable
return status === 429 || (status >= 500 && status < 600);
}
3. Timeout handling
async function generatePDFWithTimeout(params, timeout = 120000) {
return Promise.race([
generatePDF(params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout),
),
]);
}
4. Error logging
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));
// Forward to your error tracker, e.g. Sentry
// Sentry.captureException(error, { contexts: { custom: context } });
}
// Usage
try {
await generatePDF(params);
} catch (error) {
logError(error, {
operation: 'generatePDF',
designId: params.designId,
fileName: params.fileName,
});
throw error;
}
Python example
import requests
import time
from typing import Dict, Any
class ReportFlowError(Exception):
"""Base class for Report Flow API errors."""
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):
"""Validation error (400)."""
class AuthenticationError(ReportFlowError):
"""Authentication error (401)."""
class NotFoundError(ReportFlowError):
"""Resource not found (404)."""
class RateLimitError(ReportFlowError):
"""Rate limit exceeded (429). Carries retry_after seconds."""
def __init__(self, status_code, message, error, retry_after: int = 0):
super().__init__(status_code, message, error)
self.retry_after = retry_after
class ServerError(ReportFlowError):
"""Server error (5xx)."""
def generate_pdf_with_retry(params: Dict[str, Any], max_retries: int = 3) -> bytes:
for attempt in range(max_retries):
try:
return generate_pdf(params)
except RateLimitError as e:
if attempt == max_retries - 1:
raise
time.sleep(max(e.retry_after, 1))
except ServerError:
if attempt == max_retries - 1:
raise
time.sleep((2 ** attempt))
except ReportFlowError:
# Non-retryable
raise
def generate_pdf(params: Dict[str, Any]) -> bytes:
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=120)
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, 'Request timed out', 'Gateway Timeout')
except requests.exceptions.RequestException as e:
raise ServerError(500, str(e), 'Network Error')
def handle_http_error(response):
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 status == 429:
retry_after = int(response.headers.get('Retry-After') or 0)
raise RateLimitError(status, message, error, retry_after)
elif 500 <= status < 600:
raise ServerError(status, message, error)
else:
raise ReportFlowError(status, message, error)
# Usage
try:
pdf_bytes = generate_pdf_with_retry({
'app_key': 'your-app-key',
'design_id': '660e8400-e29b-41d4-a716-446655440000',
'version': 1,
'file_name': 'invoice.pdf',
'data': {
'customerName': 'John Doe',
'invoiceNumber': 'INV-2024-001',
},
})
with open('invoice.pdf', 'wb') as f:
f.write(pdf_bytes)
except ValidationError as e:
print(f"Validation error: {e.message}")
except AuthenticationError:
print("Authentication error: check your appkey")
except NotFoundError as e:
print(f"Not found: {e.message}")
except RateLimitError as e:
print(f"Rate limited; would retry after {e.retry_after}s")
except ServerError:
print("Server error: please retry shortly")
except ReportFlowError as e:
print(f"Error: {e}")
Troubleshooting
Common errors
| Error | Cause | Fix |
|---|---|---|
designId must be a UUID | The designId is not a valid UUID | Use a UUID (e.g. 550e8400-e29b-41d4-a716-446655440000) |
ファイル名に使用できない文字が含まれています | The filename contains / \ : * ? " < > | or a control character | Remove or replace those characters |
認証情報が不正です | Authentication failed (server returns this Japanese string for "Invalid credentials") | Verify the appkey value |
認証方式ヘッダーが存在しません | The appkey header is missing (server returns this Japanese string for "Authentication header is missing") | Add appkey: <key> to the request |