Skip to main content

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

CodeMeaningNotes
200 OKSuccessThe request was processed normally
202 AcceptedAcceptedAsynchronous 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 appkey header 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 appkey header 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-After and 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

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

ErrorCauseFix
designId must be a UUIDThe designId is not a valid UUIDUse a UUID (e.g. 550e8400-e29b-41d4-a716-446655440000)
ファイル名に使用できない文字が含まれていますThe filename contains / \ : * ? " < > | or a control characterRemove 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

Next steps