# Report Flow API Documentation
> Developer documentation for Report Flow API
Source: https://doc.re-port-flow.com/en
Generated: 2026-06-12T01:38:37.919Z
---
# docs/authentication/api-keys.md
# API Key Management
This page explains how to obtain and manage the API keys required to use the Report Flow API.
## Obtaining API Keys
### 1. Create a Workspace
Log in to the Report Flow admin panel (`https://re-port-flow.com`) and create a new workspace.
The application key is generated automatically when the workspace is created.
### 2. Check API Integration Information
Open the workspace details screen → "API Keys" tab to see the following:
- **API URL**: `https://api.re-port-flow.com/` (copy button available)
- **Application Key**: `ak_xxxxxxxxxxxxxxxx` (copy button available)
Click the copy button on the right side of each item to copy the value to the clipboard.
:::info
**One application key per workspace**
Each workspace is bound to a single application key. The key can be regenerated periodically.
:::
## Managing API Keys
### Inspecting the key
You can check the current application key information from the "API Keys" tab on the workspace details screen:
- **API URL**: `https://api.re-port-flow.com/` (copyable)
- **Application Key**: `ak_xxxxxxxxxxxxxxxx` (copyable)
- **Last Rotation Date**: `2024-02-14`
## Security Recommendations
### Manage with environment variables
**Recommended:** keep the API key in environment variables, never hard-coded in source.
```bash
# .env file
REPORT_FLOW_APP_KEY=ak_xxxxxxxxxxxxxxxx
```
```javascript
// Usage
const appKey = process.env.REPORT_FLOW_APP_KEY;
```
### Add to .gitignore
```gitignore
# Exclude files containing API keys from Git
.env
.env.local
.env.production
secrets.json
```
### Rotate periodically
For improved security, we recommend regenerating the application key **every 3 months**.
:::tip
A warning is shown in the "API Keys" section of the workspace details screen if more than 90 days have passed since the last rotation.
:::
**UI rotation steps:**
1. Open the workspace details screen → "API Keys" tab
2. Click "Regenerate Application Key"
3. Confirm in the modal
4. **Copy the displayed new key and apply it in your application**
5. Verify the application still works
**API rotation:**
```bash
curl -X POST \
https://api.re-port-flow.com/v1/workspace/{workspaceId}/application-token/regenerate \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json'
```
Response example:
```json
{
"applicationKey": "ak_xxxxxxxxxxxxxxxx",
"lastRotatedAt": "2024-02-14T10:30:00.000Z"
}
```
:::warning
When the application key is regenerated, the **old key is invalidated immediately**. To avoid downtime, propagate the new key to your application configuration as quickly as possible.
:::
## Security
Report Flow API keys are managed with industry-standard security measures:
- Application keys are stored encrypted
- All API operations are recorded in audit logs
- Only HTTPS communication is supported; plain text is rejected
:::tip
For maximum security we recommend periodic key rotation (every 3 months).
:::
### Production security requirements
For HTTPS / TLS version requirements, see [Authentication overview](./overview.md).
### Other best practices
#### Storing the API key
- Keep it in environment variables, never hard-coded
- Add `.env` to `.gitignore`
- Prefer a secret manager (AWS Secrets Manager, etc.)
#### Access control
- Issue API keys only to the workspaces that actually need them
- Regenerate keys immediately when no longer needed
- Audit access logs periodically
For limits and quotas, see the [Limitations page](../limitations.md).
## Troubleshooting
### Regenerating the application key
The application key is visible in the "API Keys" tab of the admin panel. If it's not visible, issue a new key as follows:
1. Open the workspace details screen → "API Keys" tab
2. Click "Regenerate Application Key"
3. Confirm the regeneration in the modal
4. Copy the displayed new key
5. Update your application configuration with the new key
### When the key isn't working
Check the following:
- [ ] The `appkey` header is set correctly
- [ ] The application key has been copied without truncation
- [ ] The key is active (verify in the admin panel)
- [ ] The header name is `appkey` (lowercase)
## Next steps
- [Authentication details](./overview)
- [PDF Generation Guide](../guides/pdf-generation)
---
# docs/authentication/oauth.md
# OAuth 2.0 Authentication
In addition to the `appkey` header, Report Flow API supports OAuth 2.0 / OpenID Connect. This is intended for third-party integrations (Make.com, etc.) and custom apps that need a per-user authorization flow or server-to-server delegation.
## When to use which method
| Use case | Recommended method |
|----------|---------------------|
| Single-workspace automation (cURL, internal batch jobs) | `appkey` header |
| Server-to-server integration (backend → Report Flow) | OAuth 2.0 **Client Credentials** |
| Per-user authorization (Make.com, third-party SaaS, personal apps) | OAuth 2.0 **Authorization Code + PKCE** |
## Endpoints
We recommend pulling the OIDC discovery document at runtime:
```
GET https://re-port-flow.com/api/v1/.well-known/openid-configuration
```
> **Important**: The OAuth flow (Discovery / Authorize / Token / UserInfo) is hosted on **`re-port-flow.com/api/v1`**, which is a different host from the protected resource API (`api.re-port-flow.com/v1/...`). The bearer token issued by the OAuth flow is sent to the protected resource API.
Main endpoints:
| Item | URL |
|------|-----|
| Issuer | `https://re-port-flow.com/api/v1` |
| Authorization | `${issuer}/oauth/authorize` |
| Token | `${issuer}/oauth/token` |
| UserInfo | `${issuer}/oauth/userinfo` |
| Protected resource API (target of `Bearer`) | `https://api.re-port-flow.com/v1/...` |
## Supported parameters
| Item | Value |
|------|-------|
| `response_type` | `code` |
| `grant_types` | `authorization_code`, `refresh_token`, `client_credentials` |
| `code_challenge_method` | `S256` (PKCE required) |
| `token_endpoint_auth_methods` | `none`, `client_secret_post`, `client_secret_basic` |
| Access token TTL | 3600 seconds (1 hour) |
## Scopes
| Scope | Description |
|-------|-------------|
| `openid` | Confirms the user's id (OIDC) |
| `profile` | Profile info (this implementation includes the email address) |
| `designs:read` | Read designs (list and detail) |
| `designs:write` | Create and edit designs |
| `templates:read` | Read templates (list and detail) |
| `templates:write` | Create and edit templates |
| `pdf:generate` | Generate PDFs from a template |
Multiple scopes are space-separated (e.g. `pdf:generate designs:read`).
## Client types
| Type | `is_public` | Authentication | Typical use |
|------|------------|----------------|-------------|
| Public Client | `true` | PKCE only (no `client_secret`) | Mobile/SPA, public integrations such as Make.com |
| Confidential Client | `false` | `client_secret` required | Server-to-server, `client_credentials` grant |
The `client_credentials` grant is **for confidential clients only**. Public clients cannot use it.
---
## Authorization Code flow (PKCE)
Use this flow when each end user authorizes the app individually — for example Make.com integrations or apps where the user operates on their own workspace.
### 1. Authorization request
```
GET https://re-port-flow.com/api/v1/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://your-app.example.com/callback
&scope=openid%20profile%20pdf:generate
&state=RANDOM_STATE
&code_challenge=BASE64URL(SHA256(verifier))
&code_challenge_method=S256
```
After the user signs in and consents, the browser is redirected to your `redirect_uri` with `code` and `state` query parameters.
### 2. Token request
```http
POST https://re-port-flow.com/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https://your-app.example.com/callback
&client_id=YOUR_CLIENT_ID
&code_verifier=ORIGINAL_VERIFIER
```
Response:
```json
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_...",
"scope": "openid profile pdf:generate"
}
```
For confidential clients, also send `client_secret` (using `client_secret_post`) or place credentials in a Basic auth header (`client_secret_basic`).
### 3. Refresh token
```http
POST https://re-port-flow.com/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=rt_...
&client_id=YOUR_CLIENT_ID
```
---
## Client Credentials flow
For server-to-server delegation. A registered confidential client obtains an access token for its own workspace, with no end user involved.
### Token request
```http
POST https://re-port-flow.com/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(client_id:client_secret)
grant_type=client_credentials
&scope=pdf:generate%20templates:read
```
Or, using `client_secret_post`:
```http
POST https://re-port-flow.com/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=pdf:generate%20templates:read
```
Response (no refresh token is issued):
```json
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "pdf:generate templates:read"
}
```
If `scope` is omitted, the full set of `allowed_scopes` registered for the client is granted.
---
## Using the access token
Send the issued access token in an `Authorization: Bearer` header instead of the `appkey` header:
```bash
curl -X POST https://api.re-port-flow.com/v1/file/sync/single \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{...}'
```
## Error responses
The token endpoint follows [RFC 6749 §5.2](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2):
```json
{
"error": "invalid_client",
"error_description": "Invalid client_secret"
}
```
Common error codes:
| `error` | When |
|---------|------|
| `invalid_client` | `client_id` / `client_secret` is invalid |
| `invalid_grant` | Authorization code or refresh token is invalid or expired |
| `invalid_scope` | Requested scope exceeds `allowed_scopes` |
| `unauthorized_client` | A public client requested `client_credentials`, etc. |
| `unsupported_grant_type` | Unsupported `grant_type` |
## Next steps
- [API Keys](./api-keys) — how to use the `appkey` header
- [Authentication overview](./overview) — choosing between methods
- [PDF Generation Guide](../guides/pdf-generation)
---
# docs/authentication/overview.md
# Authentication
Report Flow API supports two authentication methods: **`appkey` header authentication** and **OAuth 2.0 / OpenID Connect**. Choose the one that fits your use case.
## When to use which
| Use case | Recommended method |
|----------|---------------------|
| Single-workspace automation (cURL, internal batch jobs) | `appkey` header |
| Server-to-server integration (backend → Report Flow) | OAuth 2.0 **Client Credentials** |
| Per-user authorization (Make.com, third-party SaaS, personal apps) | OAuth 2.0 **Authorization Code + PKCE** |
See [OAuth 2.0 Authentication](./oauth) for the OAuth flows. This page covers the `appkey` method.
## `appkey` method
Send the `appkey` header (lowercase) on every request:
```http
appkey: your-application-key
```
## API endpoint
**Base URL**: `https://api.re-port-flow.com/v1`
Examples:
- Single PDF generation: `https://api.re-port-flow.com/v1/file/sync/single`
- Design parameters: `https://api.re-port-flow.com/v1/file/design/parameter/{designId}`
## Authentication Errors
If authentication fails, the following errors are returned:
### 401 Unauthorized
```json
{
"statusCode": 401,
"message": "認証情報が不正です",
"error": "Unauthorized"
}
```
(The server returns the message in Japanese; it translates to "Invalid credentials".)
**Cause:**
- The `appkey` header is invalid or has been revoked
### 412 Precondition Failed
```json
{
"statusCode": 412,
"message": "認証方式ヘッダーが存在しません",
"error": "Precondition Failed"
}
```
(The server returns the message in Japanese; it translates to "Authentication header is missing".)
**Cause:**
- The `appkey` header is missing
## Security Best Practices
### 1. Secure API Key Storage
```javascript
// ❌ Bad: Hardcoded in source code
const APP_KEY = 'hardcoded-key';
// ✅ Good: Use environment variables
const APP_KEY = process.env.REPORT_FLOW_APP_KEY;
```
### 2. Use HTTPS
All API requests must use HTTPS. HTTP requests are not accepted.
### 3. Key Rotation
Regularly regenerate API keys and invalidate old keys.
### 4. Scope Limitation
Use different API keys for production and development environments to separate access.
## Sample Code
### cURL
```bash
curl -X POST https://api.re-port-flow.com/v1/file/sync/single \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{...}'
```
### JavaScript
```javascript
const headers = {
'appkey': process.env.REPORT_FLOW_APP_KEY,
'Content-Type': 'application/json'
};
```
### Python
```python
import os
headers = {
'appkey': os.environ['REPORT_FLOW_APP_KEY'],
'Content-Type': 'application/json'
}
```
## OAuth 2.0 Authentication
For server-to-server integrations or per-user authorization, use OAuth 2.0 instead of `appkey`.
- **Authorization Code + PKCE**: User authorization flow for Make.com, custom apps, etc.
- **Client Credentials**: Server-to-server integration from your backend to Report Flow
See [OAuth 2.0 Authentication](./oauth) for details.
## Next Steps
- [How to obtain API keys](./api-keys)
- [OAuth 2.0 Authentication](./oauth)
- [PDF Generation Guide](../guides/pdf-generation)
---
# docs/guides/async-workflows.md
# Async Workflows
How to use the async API and recommended patterns.
## What the async API is
A subset of the Report Flow API endpoints support asynchronous processing:
| Operation | Sync | Async |
|-----------|------|-------|
| Single PDF | `/file/sync/single` | `/file/async/single` |
| Multiple PDFs | `/file/sync/multiple` | `/file/async/multiple` |
## Why use async
### 1. Avoid timeouts
Sync endpoints time out at 120 seconds. Async endpoints have no such cap, so they handle long-running jobs gracefully.
```javascript
// ❌ Sync: may exceed 120s on large payloads
const pdf = await generateSyncPDF(largeData);
// ✅ Async: no client-side wait
const { url } = await generateAsyncPDF(largeData);
```
### 2. Background processing
You can return a response to the user immediately and finish the work behind the scenes.
```javascript
// Accept the user's request right away
app.post('/api/generate-report', async (req, res) => {
// Kick off PDF generation in the background
const { url, fileId } = await startPDFGeneration(req.body);
// Respond immediately
res.json({
message: 'Report generation started',
downloadUrl: url,
fileId
});
// Notify by email when finished (background)
notifyWhenComplete(fileId, req.user.email);
});
```
### 3. Parallel bulk generation
You can fan out and generate many PDFs in parallel.
```javascript
// Generate 100 PDFs in parallel
const requests = invoices.map(invoice =>
generateAsyncPDF({
designId: 'invoice-template',
content: invoice
})
);
const results = await Promise.all(requests);
console.log(`Generated ${results.length} PDFs`);
```
## Bulk-processing patterns
### Pattern 1: Parallel execution (small/medium scale)
```javascript
async function generateBulkPDFs(items, concurrency = 5) {
const chunks = chunkArray(items, concurrency);
const results = [];
for (const chunk of chunks) {
const promises = chunk.map(item =>
generateAsyncPDF({
designId: 'template-id',
content: item
})
);
const chunkResults = await Promise.all(promises);
results.push(...chunkResults);
console.log(`${results.length}/${items.length} done`);
}
return results;
}
function chunkArray(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
// Usage: 100 items, 5 in parallel
const invoices = [...]; // 100 items
const results = await generateBulkPDFs(invoices, 5);
```
### Pattern 2: Queue-based (large scale)
```javascript
import Queue from 'bull';
// Create a Redis-backed queue
const pdfQueue = new Queue('pdf-generation', {
redis: {
host: 'localhost',
port: 6379
}
});
// Worker
pdfQueue.process(async (job) => {
const { designId, content } = job.data;
// Call the PDF generation API
const result = await generateAsyncPDF({ designId, content });
// Update progress
await job.progress(100);
return result;
});
// Enqueue jobs
async function enqueuePDFGeneration(items) {
const jobs = items.map(item =>
pdfQueue.add({
designId: 'template-id',
content: item
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
})
);
return Promise.all(jobs);
}
// Listen for events
pdfQueue.on('completed', (job, result) => {
console.log(`Job ${job.id} completed:`, result);
});
pdfQueue.on('failed', (job, err) => {
console.error(`Job ${job.id} failed:`, err);
});
```
### Pattern 3: Batch processing (very large scale)
```javascript
// For 1000+ items, prefer the multi-file generation endpoint
async function generateMassiveBulk(items) {
const batchSize = 100; // align with the API limit
const batches = chunkArray(items, batchSize);
const results = [];
for (const [index, batch] of batches.entries()) {
console.log(`Processing batch ${index + 1}/${batches.length}…`);
// Multi-file async generation
const { url, files } = await generateMultiplePDFsAsync({
designId: 'template-id',
contents: batch.map(item => ({
fileName: `${item.id}.pdf`,
params: item
}))
});
results.push({ batchIndex: index, url, files });
// Throttle for rate limits
if (index < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
```
## Error handling
### Retry logic
```javascript
async function generateWithRetry(params, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await generateAsyncPDF(params);
} catch (error) {
const isLastAttempt = attempt === maxRetries - 1;
if (isServerError(error) && !isLastAttempt) {
const delay = 1000 * Math.pow(2, attempt);
console.log(`Retry ${attempt + 1}/${maxRetries} (after ${delay}ms)`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
function isServerError(error) {
return error.response?.status >= 500;
}
```
### Partial-failure handling
```javascript
async function generateWithFallback(items) {
const results = [];
const errors = [];
for (const item of items) {
try {
const result = await generateAsyncPDF({
designId: 'template-id',
content: item
});
results.push({ item, result, status: 'success' });
} catch (error) {
console.error(`Failed to generate item ${item.id}:`, error);
errors.push({ item, error, status: 'failed' });
}
}
return {
results,
errors,
summary: {
total: items.length,
success: results.length,
failed: errors.length
}
};
}
```
## Python example
```python
import asyncio
import aiohttp
from typing import List, Dict, Any
async def generate_pdf_async(session: aiohttp.ClientSession, params: Dict[str, Any]) -> Dict:
"""Generate a PDF asynchronously."""
url = "https://api.re-port-flow.com/v1/file/async/single"
headers = {
'appkey': params['app_key'],
'Content-Type': 'application/json'
}
data = {
'designId': params['design_id'],
'version': params['version'],
'content': params['content']
}
async with session.post(url, headers=headers, json=data) as response:
response.raise_for_status()
return await response.json()
async def generate_bulk_pdfs(
items: List[Dict],
app_key: str,
design_id: str,
version: int = 1,
concurrency: int = 5,
):
"""Generate multiple PDFs in parallel, sharing one ClientSession so connection pooling is effective."""
semaphore = asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession() as session:
async def generate_with_limit(item):
async with semaphore:
return await generate_pdf_async(session, {
'app_key': app_key,
'design_id': design_id,
'version': version,
'content': item,
})
tasks = [generate_with_limit(item) for item in items]
return await asyncio.gather(*tasks)
# Usage
items = [...] # data to generate from
results = asyncio.run(generate_bulk_pdfs(
items,
app_key='your-app-key',
design_id='template-id',
version=1,
concurrency=10,
))
```
## Best practices
1. **Pick a sensible concurrency level**: balance server load and throughput.
2. **Use exponential backoff**: lengthen polling intervals progressively.
3. **Set timeouts**: don't wait forever.
4. **Log errors**: keep track of failed items.
5. **Show progress**: keep users informed.
## Next steps
- [PDF Generation Guide](./pdf-generation)
- [Error Handling](./error-handling)
---
# docs/guides/endpoints/async-multiple.md
# Async Multiple PDF Generation (ZIP)
The `POST /file/async/multiple` endpoint generates multiple PDF files asynchronously from the specified design and multiple parameters, and saves them as a ZIP file.
## Endpoint Information
- **URL**: `https://api.re-port-flow.com/v1/file/async/multiple`
- **Method**: `POST`
- **Authentication**: `appkey` header required
- **Timeout**: None (async processing)
- **Request Size Limit**: 50MB (Base64-encoded; ~37MB raw equivalent)
## Usage Examples
### cURL
```bash
curl -X POST https://api.re-port-flow.com/v1/file/async/multiple \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"contents": [
{
"fileName": "invoice_001.pdf",
"shareType": "01",
"passcodeEnabled": false,
"params": { "customerName": "John Doe", "invoiceNumber": "INV-001" }
},
{
"fileName": "invoice_002.pdf",
"shareType": "01",
"passcodeEnabled": false,
"params": { "customerName": "Jane Smith", "invoiceNumber": "INV-002" }
}
]
}'
```
### JavaScript
```javascript
async function generateMultiplePDFsAsync(designId, contents) {
const response = await axios.post(
`https://api.re-port-flow.com/v1/file/async/multiple`,
{
designId,
version: 1,
contents
},
{
headers: {
'appkey': process.env.APP_KEY
}
}
);
const { requestId, url, files } = response.data;
console.log('ZIP generation started:', { requestId, url, files });
return { requestId, url, files };
}
```
## Request Parameters
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `designId` | string (UUID) | ✓ | Design ID |
| `version` | integer | ✓ | Version number |
| `contents` | array | ✓ | Array of ContentDto (minimum 1 item) |
| `contents[].fileName` | string | ✓ | File name (anything except `/ \ : * ? " < > \|` and control characters is allowed; **must be unique within the array**, case-insensitive) |
| `contents[].shareType` | string | - | Share type (request side uses numeric codes). `"01"` = workspace share (default) / `"02"` = invited-only / `"03"` = public URL share. The **response** `share.shareType` returns the human-readable name. |
| `contents[].passcodeEnabled` | boolean | - | Enable passcode protection (default: `false`). |
| `contents[].passthrough` | object | - | Per-file arbitrary metadata echoed back in `files[].passthrough`. |
| `contents[].params` | object | ✓ | Parameters ([check structure via Design Parameters API](./design-parameters.md)) |
## Response
### Success (202 Accepted)
```json
{
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://api.re-port-flow.com/v1/file/download/{requestId}",
"files": [
{
"fileName": "invoice_001.pdf",
"fileId": "aaa111",
"share": {
"shareType": "workspace",
"url": "https://app.re-port-flow.com/file/{requestId}/aaa111",
"passcodeEnabled": false
}
},
{
"fileName": "invoice_002.pdf",
"fileId": "bbb222",
"share": {
"shareType": "workspace",
"url": "https://app.re-port-flow.com/file/{requestId}/bbb222",
"passcodeEnabled": false
}
}
]
}
```
| Field | Type | Description |
|-------|------|-------------|
| `requestId` | string (UUID) | Request ID (used with the download endpoint) |
| `url` | string (URI) | ZIP download URL |
| `files` | array | Information for each generated PDF file |
| `files[].fileName` | string | PDF file name |
| `files[].fileId` | string | Individual file ID (used with individual download endpoint) |
| `files[].passthrough` | object | Value of `contents[].passthrough` from the request (only when specified) |
| `files[].share.shareType` | string | Share type (`workspace` / `invited` / `public`) |
| `files[].share.url` | string | File view URL |
| `files[].share.passcodeEnabled` | boolean | Passcode enabled flag |
| `files[].share.passcode` | string | Server-generated passcode (only when `passcodeEnabled=true`, immediately after generation) |
## Error Responses
Same error responses as [Sync Single PDF Generation](./sync-single.md#errors), plus the following:
### 400 Bad Request (duplicate fileName)
```json
{
"statusCode": 400,
"message": [
"Duplicate fileName found in contents (case-insensitive). Each file must have a unique name."
],
"error": "Bad Request"
}
```
**Cause**: Multiple entries in `contents` share the same `fileName` (including case-insensitive matches).
## Use Cases
### Bulk Invoice Generation
```javascript
async function generateBulkInvoices(invoices) {
const batchSize = 100;
const batches = chunkArray(invoices, batchSize);
const results = [];
for (const [index, batch] of batches.entries()) {
console.log(`Processing batch ${index + 1}/${batches.length}...`);
const contents = batch.map(invoice => ({
fileName: `invoice_${invoice.number}.pdf`,
shareType: '01',
passcodeEnabled: false,
params: invoice
}));
const result = await generateMultiplePDFsAsync('template-id', contents);
results.push(result);
// Rate limiting
if (index < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
```
## Next Steps
- [Bulk ZIP Download](./download.md#zip-bulk-download) - How to download generated ZIP files
- [Async Workflows](../async-workflows.md) - Best practices for bulk generation
---
# docs/guides/endpoints/async-single.md
# Single PDF Async Generation
The `POST /file/async/single` endpoint generates a single PDF asynchronously from the specified design and parameters. It returns a `requestId` and file information immediately, so the client never has to wait for rendering.
## Endpoint
- **URL**: `https://api.re-port-flow.com/v1/file/async/single`
- **Method**: `POST`
- **Auth**: `appkey` header required
- **Timeout**: none (async processing)
- **Request body limit**: 50MB (after Base64 encoding, roughly 37MB of binary data)
## Example
### cURL
```bash
curl -X POST https://api.re-port-flow.com/v1/file/async/single \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"shareType": "01",
"passcodeEnabled": false,
"params": {
"customerName": "John Doe",
"invoiceNumber": "INV-2024-001",
"amount": 10000
}
}
}'
```
## Request parameters
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `designId` | string (UUID) | ✓ | Design ID |
| `version` | integer | ✓ | Design version |
| `content.fileName` | string | ✓ | File name (any character is allowed except `/ \ : * ? " < > |` and control characters) |
| `content.shareType` | string | - | Share type **on the request side** is a numeric code: `"01"` = workspace (default), `"02"` = invitee, `"03"` = public URL. **In the response**, `share.shareType` is returned as the human-readable name (`workspace` / `invited` / `public`) |
| `content.passcodeEnabled` | boolean | - | Whether to enable passcode protection (default `false`) |
| `content.passthrough` | object | - | Arbitrary metadata that will be echoed back in `files[].passthrough` (e.g. `{ "pageId": "abc123" }`) |
| `content.params` | object | ✓ | Template parameters. The expected structure is available from the [design parameter API](./design-parameters.md) |
## Response
### Success (202 Accepted)
```json
{
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://api.re-port-flow.com/v1/file/download/{requestId}",
"files": [
{
"fileName": "invoice.pdf",
"fileId": "7f3d1a2b-4c5e-6f7a-8b9c-0d1e2f3a4b5c",
"passthrough": { "pageId": "abc123" },
"share": {
"shareType": "workspace",
"url": "https://app.re-port-flow.com/file/{requestId}/{fileId}",
"passcodeEnabled": false
}
}
]
}
```
| Field | Type | Description |
|-------|------|-------------|
| `requestId` | string (UUID) | Request ID, used by the download endpoint |
| `url` | string (URI) | ZIP download URL |
| `files` | array | Generated files |
| `files[].fileName` | string | File name |
| `files[].fileId` | string | File ID, used by the per-file download endpoint |
| `files[].passthrough` | object | The `content.passthrough` value supplied on the request (only when set) |
| `files[].share.shareType` | string | Share type (`workspace` / `invited` / `public`) |
| `files[].share.url` | string | Sharable URL |
| `files[].share.passcodeEnabled` | boolean | Whether passcode protection is enabled |
| `files[].share.passcode` | string | Server-generated passcode (only when `passcodeEnabled=true` AND immediately after generation) |
:::info When to use `passthrough`
ReportFlow does **not** echo back `params` (the data used to render the
PDF) on responses or webhooks, both for payload size and to avoid leaking
business data to webhook endpoints.
If you need to know which business record a PDF corresponds to, put your
own DB id (or any opaque token) into `passthrough` on the request. The
exact value comes back on the response and the webhook unchanged.
```json
{
"fileName": "invoice.pdf",
"passthrough": { "invoiceId": "INV-001", "tenantId": "acme" },
"params": { "customerName": "John Doe", "amount": 10000 }
}
```
When the webhook arrives, look up your DB by `invoiceId` to find the
record to update. `params` (the customer name, amount, etc.) is never
sent off-server.
:::
### Errors
Errors are the same shape as the synchronous endpoint. See [Single PDF Sync Generation — Errors](./sync-single.md#errors).
## Async flow
```
1. Client → API: send generation request
↓
2. API → Client: returns requestId / url / files immediately (202 Accepted)
↓
3. API: starts background PDF generation
↓
4. API: uploads the result to S3 when done
↓
5. Client: download the PDF via the returned `url` or
/v1/file/download/{requestId}/{fileId}
```
## Use cases
### Case 1: background generation
Accept the user request immediately and let the rendering happen in the background.
```javascript
app.post('/api/generate-report', async (req, res) => {
const response = await axios.post(
'https://api.re-port-flow.com/v1/file/async/single',
{
designId: '...',
version: 1,
content: { fileName: 'report.pdf', params: req.body },
},
{ headers: { appkey: process.env.APP_KEY } },
);
const { requestId, url, files } = response.data;
res.status(202).json({
message: 'Report generation started',
requestId,
downloadUrl: url,
fileId: files[0].fileId,
});
});
```
### Case 2: webhook notifications (recommended)
Use ReportFlow's built-in webhook to be notified when generation finishes — no polling required.
See the [Webhook guide](../webhooks.md) for details, including HMAC-SHA256 signature verification.
## Next steps
- [Multiple PDF Async Generation](./async-multiple.md) — generate many PDFs in one async call
- [Async Workflows](../async-workflows.md) — best practices for high-volume generation
- [File Download](./download.md) — how to download the generated files
---
# docs/guides/endpoints/design-parameters.md
# Design Parameters
The `GET /file/design/parameter/{designId}` endpoint retrieves the parameter structure for a specified design ID. You can check the schema for parameters needed for PDF or thumbnail generation.
## Endpoint Information
- **URL**: `https://api.re-port-flow.com/v1/file/design/parameter/{designId}`
- **Method**: `GET`
- **Authentication**: `appkey` header required
## Usage Examples
### cURL
```bash
# Get latest version parameters
curl -X GET https://api.re-port-flow.com/v1/file/design/parameter/550e8400-e29b-41d4-a716-446655440000 \
-H "appkey: your-application-key"
# Get specific version parameters
curl -X GET "https://api.re-port-flow.com/v1/file/design/parameter/550e8400-e29b-41d4-a716-446655440000?version=1" \
-H "appkey: your-application-key"
```
### JavaScript
```javascript
async function getDesignParameters(designId, version = null) {
const baseUrl = 'https://api.re-port-flow.com/v1';
const url = version
? `${baseUrl}/file/design/parameter/${designId}?version=${version}`
: `${baseUrl}/file/design/parameter/${designId}`;
const response = await axios.get(url, {
headers: {
'appkey': process.env.APP_KEY
}
});
return response.data;
}
// Usage example
const schema = await getDesignParameters('550e8400-...');
console.log('Parameter schema:', schema);
```
### Python
```python
def get_design_parameters(design_id, version=None):
base_url = "https://api.re-port-flow.com/v1"
url = f"{base_url}/file/design/parameter/{design_id}"
if version:
url += f"?version={version}"
headers = {
'appkey': os.getenv('APP_KEY')
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
# Usage example
schema = get_design_parameters('550e8400-...')
print('Parameter schema:', schema)
```
## Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `designId` | string (UUID) | ✓ | Design ID (path parameter) |
| `version` | integer | - | Version number (query parameter, defaults to latest) |
## Response
### Success (200 OK)
```json
{
"customerName": "string",
"invoiceNumber": "string",
"amount": "number",
"items": [
{
"name": "string",
"price": "number",
"quantity": "number"
}
],
"issueDate": "date"
}
```
**Field types**:
| Type | Description | Example |
|------|-------------|---------|
| `string` | String | `"John Doe"` |
| `number` | Number | `1000` |
| `date` | Date (ISO 8601) | `"2024-02-12"` |
| `object` | Nested object | `{ "name": "value" }` |
| `array` | Array | `[{ "item": 1 }]` |
### Errors
#### 404 Not Found
```json
{
"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").)
**Cause**: Specified design ID or version does not exist
## Use Cases
### Dynamic Form Generation
Generate input forms dynamically from design parameters.
```javascript
async function createDynamicForm(designId) {
// Get parameter schema
const schema = await getDesignParameters(designId);
// Generate form fields from schema
const formFields = Object.entries(schema).map(([key, type]) => {
if (type === 'string') {
return ``;
} else if (type === 'number') {
return ``;
} else if (type === 'date') {
return ``;
} else if (Array.isArray(type)) {
// Array type
return `
`;
}
});
return formFields.join('\n');
}
```
### Validation
```javascript
function validateParams(params, schema) {
const errors = [];
for (const [key, type] of Object.entries(schema)) {
if (!(key in params)) {
errors.push(`Required parameter ${key} is missing`);
continue;
}
const value = params[key];
if (type === 'string' && typeof value !== 'string') {
errors.push(`${key} must be a string`);
} else if (type === 'number' && typeof value !== 'number') {
errors.push(`${key} must be a number`);
} else if (type === 'date') {
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
errors.push(`${key} must be in ISO 8601 date format`);
}
}
}
return errors;
}
// Usage example
const schema = await getDesignParameters('550e8400-...');
const params = {
customerName: 'John Doe',
invoiceNumber: 'INV-001',
amount: 10000
};
const errors = validateParams(params, schema);
if (errors.length > 0) {
console.error('Validation errors:', errors);
}
```
## Best Practices
### 1. Cache Parameter Schema
Parameter schemas don't change unless the design version changes. Cache and reuse them.
```javascript
const schemaCache = new Map();
async function getDesignParametersCached(designId, version) {
const cacheKey = `${designId}:${version}`;
if (schemaCache.has(cacheKey)) {
return schemaCache.get(cacheKey);
}
const schema = await getDesignParameters(designId, version);
schemaCache.set(cacheKey, schema);
return schema;
}
```
### 2. Validate Before PDF Generation
Get parameter schema in advance and validate to detect errors early.
```javascript
async function generatePDFSafely(params) {
// 1. Get parameter schema
const schema = await getDesignParameters(params.designId, params.version);
// 2. Validate
const errors = validateParams(params.data, schema);
if (errors.length > 0) {
throw new Error(`Parameter errors: ${errors.join(', ')}`);
}
// 3. Generate PDF
return await generatePDF(params);
}
```
## Next Steps
- [Sync Single PDF Generation](./sync-single.md) - Generate PDF using parameters
- [Error Handling](../error-handling.md) - Handle validation errors
---
# docs/guides/endpoints/download.md
# File Download
Download endpoints allow you to retrieve generated PDF files.
## ZIP Bulk Download
### Endpoint Information
- **URL**: `https://api.re-port-flow.com/v1/file/download/{uuid}`
- **Method**: `GET`
- **Authentication**: `appkey` header required
### Usage Example
```bash
curl -X GET https://api.re-port-flow.com/v1/file/download/550e8400-e29b-41d4-a716-446655440000 \
-H "appkey: your-application-key" \
--output files.zip
```
### JavaScript Example
```javascript
async function downloadZip(uuid) {
const response = await axios.get(
`https://api.re-port-flow.com/v1/file/download/${uuid}`,
{
headers: {
'appkey': process.env.APP_KEY
},
responseType: 'arraybuffer'
}
);
fs.writeFileSync('files.zip', response.data);
return response.data;
}
```
## Individual File Download
### Endpoint Information
- **URL**: `https://api.re-port-flow.com/v1/file/download/{uuid}/{fileId}`
- **Method**: `GET`
- **Authentication**: `appkey` header required
### Usage Example
```bash
curl -X GET https://api.re-port-flow.com/v1/file/download/550e8400-e29b-41d4-a716-446655440000/file_123456 \
-H "appkey: your-application-key" \
--output invoice.pdf
```
### JavaScript Example
```javascript
async function downloadSingleFile(uuid, fileId) {
const response = await axios.get(
`https://api.re-port-flow.com/v1/file/download/${uuid}/${fileId}`,
{
headers: {
'appkey': process.env.APP_KEY
},
responseType: 'arraybuffer'
}
);
const returnedFileId = response.headers['file-id'];
console.log('Downloaded file ID:', returnedFileId);
return response.data;
}
```
## Error Handling
### 404 Not Found
**Cause**:
- Specified UUID or fileId does not exist
- File retention period has expired
**Solution**:
- Verify UUID/fileId is correct
- Generate file again if expired
## Best Practices
### 1. Download Immediately After Generation
Generated files have a limited retention period. Download immediately after async generation completes.
### 2. Verify File ID
Check the File-ID header to verify you downloaded the correct file.
```javascript
const fileId = response.headers['file-id'];
if (fileId !== expectedFileId) {
throw new Error('Downloaded wrong file');
}
```
## Next Steps
- [Async Single PDF](./async-single.md) - Generate files for download
- [Async Multiple PDFs](./async-multiple.md) - Generate ZIP files for download
---
# docs/guides/endpoints/sync-multiple.md
# Sync Multiple PDF Generation (ZIP)
The `POST /file/sync/multiple` endpoint generates multiple PDF files synchronously from the specified design and multiple parameters, and returns them as a ZIP file.
## Endpoint Information
- **URL**: `https://api.re-port-flow.com/v1/file/sync/multiple`
- **Method**: `POST`
- **Authentication**: `appkey` header required
- **Timeout**: 120 seconds (depends on file count)
- **Request Size Limit**: 50MB (Base64-encoded; ~37MB raw equivalent)
## Usage Examples
### cURL
```bash
curl -X POST https://api.re-port-flow.com/v1/file/sync/multiple \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"contents": [
{
"fileName": "invoice_001.pdf",
"shareType": "01",
"passcodeEnabled": false,
"params": {
"customerName": "John Doe",
"invoiceNumber": "INV-001"
}
},
{
"fileName": "invoice_002.pdf",
"shareType": "02",
"passcodeEnabled": false,
"params": {
"customerName": "Jane Smith",
"invoiceNumber": "INV-002"
}
}
]
}' \
--output invoices.zip
```
### JavaScript
```javascript
const axios = require('axios');
const fs = require('fs');
async function generateMultiplePDFs(designId, contents) {
const response = await axios.post(
`https://api.re-port-flow.com/v1/file/sync/multiple`,
{
designId,
version: 1,
contents
},
{
headers: {
'appkey': process.env.APP_KEY
},
responseType: 'arraybuffer'
}
);
// Get file mapping from X-File-Mapping header
// X-File-Mapping is URL-encoded JSON; decode with decodeURIComponent → JSON.parse.
const fileMapping = JSON.parse(decodeURIComponent(response.headers['x-file-mapping']));
console.log('Generated files:', fileMapping);
return response.data; // ZIP binary
}
// Usage example
const contents = [
{
fileName: 'invoice_001.pdf',
params: { customerName: 'John Doe', invoiceNumber: 'INV-001' }
},
{
fileName: 'invoice_002.pdf',
params: { customerName: 'Jane Smith', invoiceNumber: 'INV-002' }
}
];
const zipData = await generateMultiplePDFs('550e8400-...', contents);
fs.writeFileSync('invoices.zip', zipData);
```
## Request Parameters
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `designId` | string (UUID) | ✓ | Design ID |
| `version` | integer | ✓ | Version number |
| `contents` | array | ✓ | Array of ContentDto (minimum 1 item) |
| `contents[].fileName` | string | ✓ | File name (anything except `/ \ : * ? " < > \|` and control characters is allowed; **must be unique within the array**, case-insensitive) |
| `contents[].shareType` | string | - | Share type (request side uses numeric codes). `"01"` = workspace share (default) / `"02"` = invited-only / `"03"` = public URL share. The **response** `share.shareType` returns the human-readable name. |
| `contents[].passcodeEnabled` | boolean | - | Enable passcode protection (default: `false`). |
| `contents[].passthrough` | object | - | Per-file string KV echoed back in the response `X-File-Mapping[].passthrough`. |
| `contents[].params` | object | ✓ | Parameters ([check structure via Design Parameters API](./design-parameters.md)) |
## Response
### Success (200 OK)
**Response Body**: ZIP file (binary)
**Response Headers**:
| Header | Description | Example |
|--------|-------------|---------|
| `Content-Type` | Content type | `application/zip` |
| `Content-Length` | File size in bytes | `307200` |
| `Content-Disposition` | File name | `attachment; filename="files.zip"` |
| `File-URL` | ZIP download URL | `https://api.re-port-flow.com/v1/file/download/{requestId}` |
| `Request-Id` | Request ID | `550e8400-e29b-41d4-a716-446655440000` |
| `X-File-Mapping` | File metadata and share info (JSON array). **URL-encoded**, so clients must call `decodeURIComponent` before `JSON.parse`. | See below |
**Structure of `X-File-Mapping`**:
```json
[
{
"fileId": "aaa111",
"fileName": "invoice_001.pdf",
"passthrough": { "pageId": "abc123" },
"share": {
"shareType": "workspace",
"url": "https://app.re-port-flow.com/file/{requestId}/aaa111",
"passcodeEnabled": false
}
},
{
"fileId": "bbb222",
"fileName": "invoice_002.pdf",
"share": {
"shareType": "invited",
"url": "https://app.re-port-flow.com/file/{requestId}/bbb222",
"passcodeEnabled": false
}
}
]
```
The `passthrough` field is included only on entries where `contents[].passthrough` was set on the request.
:::info When to use `passthrough`
ReportFlow does **not** echo back `params` (the data used to render the
PDF) on responses or webhooks, both for payload size and to avoid
leaking business data.
If you need to know which business record each PDF corresponds to, put
your own DB id (or any opaque token) into `passthrough` on the request.
The exact value comes back unchanged on responses and webhooks.
```json
{
"fileName": "invoice.pdf",
"passthrough": { "invoiceId": "INV-001", "tenantId": "acme" },
"params": { "customerName": "John Doe", "amount": 10000 }
}
```
When the webhook arrives, look up your DB by `invoiceId` to find the
record to update. `params` (the customer name, amount, etc.) is never
sent off-server.
:::
### Errors
In addition to the same error responses as [Single PDF Sync Generation — Errors](./sync-single.md#errors), the following error may be returned:
#### 400 Bad Request (duplicate `fileName`)
```json
{
"statusCode": 400,
"message": [
"Duplicate fileName in contents (case-insensitive). Each file must have a unique name."
],
"error": "Bad Request"
}
```
**Cause**: two or more entries in `contents` share the same `fileName` (case-insensitive).
## Use Cases
### Bulk Monthly Invoice Generation
```javascript
async function generateMonthlyInvoices(month) {
// Get invoice data for the month
const invoices = await getInvoicesByMonth(month);
// Create ContentDto array
const contents = invoices.map(invoice => ({
fileName: `invoice_${invoice.number}.pdf`,
params: invoice
}));
// Bulk generation
const zipData = await generateMultiplePDFs('invoice-template-id', contents);
// Save ZIP file
fs.writeFileSync(`invoices_${month}.zip`, zipData);
}
```
## Next Steps
- [Async Multiple PDF Generation](./async-multiple.md) - To avoid timeout
- [File Download](./download.md) - How to download generated ZIP files
---
# docs/guides/endpoints/sync-single.md
# Sync Single PDF Generation
The `POST /file/sync/single` endpoint generates a single PDF file synchronously from the specified design and parameters.
## Endpoint Information
- **URL**: `https://api.re-port-flow.com/v1/file/sync/single`
- **Method**: `POST`
- **Authentication**: `appkey` header required
- **Timeout**: 120 seconds
- **Request Size Limit**: 50MB (Base64-encoded; ~37MB raw equivalent)
## Usage Examples
### cURL
```bash
curl -X POST https://api.re-port-flow.com/v1/file/sync/single \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"shareType": "01",
"passcodeEnabled": false,
"params": {
"customerName": "John Doe",
"invoiceNumber": "INV-2024-001",
"amount": 10000
}
}
}' \
--output invoice.pdf
```
### JavaScript (Node.js)
```javascript
import axios from 'axios';
import fs from 'fs';
async function generatePDF(params) {
try {
const response = await axios.post(
'https://api.re-port-flow.com/v1/file/sync/single',
{
designId: params.designId,
version: params.version,
content: {
fileName: params.fileName,
shareType: params.shareType ?? '01',
passcodeEnabled: params.passcodeEnabled ?? false,
params: params.data
}
},
{
headers: {
'appkey': process.env.APP_KEY,
'Content-Type': 'application/json'
},
responseType: 'arraybuffer'
}
);
fs.writeFileSync(params.fileName, response.data);
// X-File-Mapping is URL-encoded JSON. Decode with decodeURIComponent → JSON.parse.
const requestId = response.headers['request-id'];
const fileUrl = response.headers['file-url'];
const fileMapping = JSON.parse(decodeURIComponent(response.headers['x-file-mapping']));
console.log('PDF generated:', { requestId, fileUrl, fileMapping });
return { data: response.data, requestId, fileUrl, fileMapping };
} catch (error) {
console.error('PDF generation error:', error.response?.data || error.message);
throw error;
}
}
generatePDF({
designId: '550e8400-e29b-41d4-a716-446655440000',
version: 1,
fileName: 'invoice.pdf',
shareType: '01',
passcodeEnabled: false,
data: {
customerName: 'John Doe',
invoiceNumber: 'INV-2024-001',
amount: 10000
}
});
```
### Python
```python
import requests
import json
from urllib.parse import unquote
def generate_pdf(params):
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'],
'shareType': params.get('share_type', '01'),
'passcodeEnabled': params.get('passcode_enabled', False),
'params': params['data']
}
}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
with open(params['file_name'], 'wb') as f:
f.write(response.content)
# X-File-Mapping is URL-encoded JSON; decode with unquote → json.loads.
request_id = response.headers.get('request-id')
file_url = response.headers.get('file-url')
file_mapping = json.loads(unquote(response.headers.get('x-file-mapping', '[]')))
return {'request_id': request_id, 'file_url': file_url, 'file_mapping': file_mapping}
result = generate_pdf({
'app_key': 'your-application-key',
'design_id': '550e8400-e29b-41d4-a716-446655440000',
'version': 1,
'file_name': 'invoice.pdf',
'share_type': '01',
'passcode_enabled': False,
'data': {
'customerName': 'John Doe',
'invoiceNumber': 'INV-2024-001',
'amount': 10000
}
})
```
## Request Parameters
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `designId` | string (UUID) | ✓ | Design ID |
| `version` | integer | ✓ | Version number |
| `content.fileName` | string | ✓ | File name (anything except `/ \\ : * ? " < > \|` and control characters is allowed) |
| `content.shareType` | string | - | Share type (request side uses numeric codes). `"01"` = workspace share (default) / `"02"` = invited-only / `"03"` = public URL share. The **response** `share.shareType` returns the human-readable name (`workspace` / `invited` / `public`). |
| `content.passcodeEnabled` | boolean | - | Enable passcode protection (default: `false`). When `true`, the response `share.passcode` returns a server-generated passcode exactly once. |
| `content.passthrough` | object | - | Arbitrary string KV that is echoed back in the response `X-File-Mapping[].passthrough`. Useful for tracking with webhooks (e.g. `{ "orderId": "...", "userId": "..." }`). |
| `content.params` | object | ✓ | Parameters embedded into the template ([check structure with the Design Parameters API](./design-parameters.md)) |
## Response
### Success (200 OK)
**Response Body**: PDF file (binary)
**Response Headers**:
| Header | Description | Example |
|--------|-------------|---------|
| `Content-Type` | Content type | `application/pdf` |
| `Content-Length` | File size in bytes | `102400` |
| `Content-Disposition` | File name | `attachment; filename="invoice.pdf"` |
| `File-URL` | URL of the workspace download endpoint | `https://api.re-port-flow.com/v1/file/download/{requestId}` |
| `Request-Id` | Request ID | `550e8400-e29b-41d4-a716-446655440000` |
| `X-File-Mapping` | File metadata and share info (JSON array). **URL-encoded**, so clients must call `decodeURIComponent` before `JSON.parse`. | See below |
**`X-File-Mapping` structure** (after decoding):
```json
[
{
"fileId": "7f3d1a2b-4c5e-6f7a-8b9c-0d1e2f3a4b5c",
"fileName": "invoice.pdf",
"passthrough": { "orderId": "ORD-2024-001" },
"share": {
"shareType": "workspace",
"url": "https://app.re-port-flow.com/file/{requestId}/{fileId}",
"passcodeEnabled": false
}
}
]
```
`passthrough` is included only when `content.passthrough` was specified in the request.
### Errors
#### 412 Precondition Failed
```json
{
"statusCode": 412,
"message": "認証方式ヘッダーが存在しません"
}
```
**Cause**: `appkey` header is missing.
#### 400 Bad Request
```json
{
"statusCode": 400,
"message": [
"designId must be a UUID",
"ファイル名に使用できない文字が含まれています(/ \\ : * ? \" < > | および制御文字は使用不可)"
],
"error": "Bad Request"
}
```
**Cause**: Invalid request parameters.
#### 401 Unauthorized
```json
{
"statusCode": 401,
"message": "認証情報が不正です",
"error": "Unauthorized"
}
```
(The server returns the message in Japanese; it translates to "Invalid credentials".)
**Cause**: Authentication failure.
**Solution**:
- Verify the value of the `appkey` header is correct.
#### 500 Internal Server Error
```json
{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}
```
## Best Practices
### 1. Timeout Handling
The sync API times out after 120 seconds. For large PDFs or complex designs, consider using the async API.
```javascript
const response = await axios.post(url, data, {
timeout: 120000 // 120 seconds
});
```
### 2. Retry Logic
For transient 5xx errors, retry with exponential backoff.
```javascript
async function generatePDFWithRetry(params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await generatePDF(params);
} catch (error) {
if (error.response?.status === 500 && i < maxRetries - 1) {
const delay = 1000 * Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
```
### 3. File Name Sanitization
```javascript
function sanitizeFileName(fileName) {
// Remove / \ : * ? " < > | and control characters
return fileName.replace(/[\/\\:*?"<>|\x00-\x1F]/g, '_');
}
```
## Use Cases
### Case 1: Instant Invoice Generation
Generate and download a PDF immediately when the user clicks the "Download Invoice" button.
```javascript
app.get('/api/invoices/:id/download', async (req, res) => {
const invoice = await getInvoice(req.params.id);
const pdf = await generatePDF({
designId: 'invoice-template-id',
version: 1,
fileName: `invoice_${invoice.number}.pdf`,
data: invoice
});
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="invoice_${invoice.number}.pdf"`);
res.send(pdf.data);
});
```
### Case 2: Preview Generation
Generate a PDF in real time when the user clicks the preview button in the design editor.
```javascript
async function showPreview(designId, params) {
const pdf = await generatePDF({
designId,
version: 1,
fileName: 'preview.pdf',
data: params
});
const blob = new Blob([pdf.data], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
}
```
## Next Steps
- [Async Single PDF](./async-single.md) — to avoid timeout
- [Sync Multiple PDFs](./sync-multiple.md) — generate multiple PDFs in one call
- [Async Workflows](../async-workflows.md) — best practices for bulk generation
---
# docs/guides/error-handling.md
# 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:
```json
{
"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.
```json
{
"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**
```json
{
"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.
```json
{
"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.
```json
{
"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.
```json
{
"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.
```json
{
"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.
```json
{
"statusCode": 413,
"message": "Payload Too Large",
"error": "Request Entity Too Large"
}
```
**What to do**:
- Optimise images (resolution / compression)
- Split the request
- See [Limitations](../limitations.md#-request-size-limit)
#### 429 Too Many Requests
**Cause**: Rate limit exceeded (applied per workspace).
```http
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](../limitations.md#-rate-limit)
### 5xx Server errors
#### 500 Internal Server Error
**Cause**: Internal server error.
```json
{
"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
```javascript
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.
```javascript
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
```javascript
async function generatePDFWithTimeout(params, timeout = 120000) {
return Promise.race([
generatePDF(params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout),
),
]);
}
```
### 4. Error logging
```javascript
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
```python
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: ` to the request |
## Next steps
- [PDF Generation Guide](./pdf-generation)
- [Async Workflows](./async-workflows)
---
# docs/guides/pdf-generation.md
# PDF Generation Guide
A comprehensive guide to PDF generation using the Report Flow API.
## Overview
There are two modes for PDF generation:
| Mode | Endpoint | Response | Use Case |
|------|----------|----------|----------|
| Sync Generation | `/file/sync/single` | PDF binary | When immediate results are needed |
| Async Generation | `/file/async/single` | `requestId` / `url` / `files` array (202 Accepted) | For batch processing or background tasks |
## Sync Generation
### Basic Usage
```bash
curl -X POST https://api.re-port-flow.com/v1/file/sync/single \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"params": {
"customerName": "John Doe",
"invoiceNumber": "INV-2024-001",
"items": [
{
"name": "Product A",
"price": 1000,
"quantity": 2
}
]
}
}
}' \
--output invoice.pdf
```
### Response Headers
```http
Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice.pdf"
Content-Length: 123456
Request-Id: 550e8400-e29b-41d4-a716-446655440000
File-URL: https://api.re-port-flow.com/v1/file/download/{requestId}
X-File-Mapping: %5B%7B%22fileId%22%3A...%7D%5D
```
`X-File-Mapping` is a **URL-encoded JSON array** containing `fileId` / `fileName` / `share` for each generated file. Clients should `decodeURIComponent` and then `JSON.parse` the value. See the [single sync PDF endpoint](./endpoints/sync-single.md) for the full schema.
### JavaScript Implementation Example
```javascript
const axios = require('axios');
const fs = require('fs');
async function generatePDF(params) {
try {
const response = await axios.post(
'https://api.re-port-flow.com/v1/file/sync/single',
{
designId: params.designId,
version: params.version,
content: {
fileName: params.fileName,
params: params.data
}
},
{
headers: {
'appkey': process.env.APP_KEY,
'Content-Type': 'application/json'
},
responseType: 'arraybuffer'
}
);
// Save to file
fs.writeFileSync(params.fileName, response.data);
// Get file URL
const requestId = response.headers['request-id'];
const fileUrl = response.headers['file-url'];
const fileMapping = JSON.parse(
decodeURIComponent(response.headers['x-file-mapping'] || '%5B%5D'),
);
return { requestId, fileUrl, fileMapping };
} catch (error) {
console.error('PDF generation error:', error.response?.data || error.message);
throw error;
}
}
// Usage example
generatePDF({
designId: '550e8400-e29b-41d4-a716-446655440000',
version: 1,
fileName: 'invoice.pdf',
data: {
customerName: 'John Doe',
invoiceNumber: 'INV-2024-001',
items: [
{ name: 'Product A', price: 1000, quantity: 2 }
]
}
});
```
## Async Generation
Use async generation for large PDF batches or to avoid timeouts.
### Basic Usage
```bash
# 1. Generation request
curl -X POST https://api.re-port-flow.com/v1/file/async/single \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"params": {...}
}
}'
# Response example (202 Accepted)
{
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://api.re-port-flow.com/v1/file/download/{requestId}",
"files": [
{
"fileName": "invoice.pdf",
"fileId": "7f3d1a2b-4c5e-6f7a-8b9c-0d1e2f3a4b5c",
"share": {
"shareType": "workspace",
"url": "https://app.re-port-flow.com/file/{requestId}/{fileId}",
"passcodeEnabled": false
}
}
]
}
```
See the [single async PDF endpoint](./endpoints/async-single.md) for the full response schema.
### JavaScript Implementation Example (Polling)
```javascript
async function generatePDFAsync(params) {
// 1. Async generation request
const response = await axios.post(
'https://api.re-port-flow.com/v1/file/async/single',
{
designId: params.designId,
version: params.version,
content: {
fileName: params.fileName,
params: params.data
}
},
{
headers: {
'appkey': process.env.APP_KEY,
'Content-Type': 'application/json'
}
}
);
const { requestId, url, files } = response.data;
// 2. Download the ZIP from `url`, or fetch a single file via
// GET /v1/file/download/{requestId}/{fileId}
const pdfResponse = await axios.get(url, {
headers: { 'appkey': process.env.APP_KEY },
responseType: 'arraybuffer',
});
return {
data: pdfResponse.data,
requestId,
url,
files,
};
}
```
## Multiple PDF Generation (ZIP)
Generate multiple PDFs at once and receive them as a ZIP file.
### Sync Generation
```javascript
async function generateMultiplePDFs(designId, contents) {
const response = await axios.post(
'https://api.re-port-flow.com/v1/file/sync/multiple',
{
designId,
version: 1,
contents // Array of ContentDto
},
{
headers: {
'appkey': process.env.APP_KEY
},
responseType: 'arraybuffer'
}
);
// Get file mapping from X-File-Mapping header
const fileMapping = JSON.parse(response.headers['x-file-mapping']);
console.log('Generated files:', fileMapping);
return response.data; // ZIP binary
}
// Usage example
const contents = [
{
fileName: 'invoice_001.pdf',
params: { customerName: 'John Doe', invoiceNumber: 'INV-001' }
},
{
fileName: 'invoice_002.pdf',
params: { customerName: 'Jane Smith', invoiceNumber: 'INV-002' }
}
];
const zipData = await generateMultiplePDFs('550e8400-...', contents);
fs.writeFileSync('invoices.zip', zipData);
```
## Parameter Structure
### Retrieving Design Parameters
Before generation, you can check the available parameter structure for a design:
```bash
curl -X GET https://api.re-port-flow.com/v1/file/design/parameter/{designId}?version=1 \
-H "appkey: your-application-key"
```
**Response Example:**
```json
{
"customerName": "string",
"invoiceNumber": "string",
"amount": "number",
"items": [
{
"name": "string",
"price": "number",
"quantity": "number"
}
],
"issueDate": "date"
}
```
### Parameter Type Mapping
| Type | Description | Example |
|------|-------------|---------|
| `string` | Text string | `"John Doe"` |
| `number` | Numeric value | `1000` |
| `date` | Date (ISO 8601) | `"2024-02-12"` |
| `object` | Nested object | `{ "name": "value" }` |
| `array` | Array | `[{ "item": 1 }]` |
## Error Handling
### Common Errors
#### 400 Bad Request - Validation Error
```json
{
"statusCode": 400,
"message": [
"designId must be a UUID",
"fileName can only contain alphanumeric characters, Japanese characters, hyphens, underscores, and dots"
],
"error": "Bad Request"
}
```
**Solution:**
- Check request body
- Verify file name format (regex: `^[a-zA-Z0-9\u3000-\uFFEF_.\-]+$`)
#### 500 Internal Server Error
```json
{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}
```
**Solution:**
- Possible temporary server error
- Implement retry logic
- Contact support if it persists
### Retry Strategy
```javascript
async function generatePDFWithRetry(params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await generatePDF(params);
} catch (error) {
if (error.response?.status === 500 && i < maxRetries - 1) {
// Retry with exponential backoff
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
continue;
}
throw error;
}
}
}
```
## Best Practices
### 1. Timeout Settings
Sync generation times out after 30 seconds. Use async generation for large PDFs.
```javascript
// Set timeout with axios
const response = await axios.post(url, data, {
timeout: 30000 // 30 seconds
});
```
### 2. File Name Sanitization
```javascript
function sanitizeFileName(fileName) {
return fileName.replace(/[^a-zA-Z0-9\u3000-\uFFEF_.\-]/g, '_');
}
```
### 3. Parameter Validation
```javascript
function validateParams(params, schema) {
// Validate against design parameter schema
for (const [key, type] of Object.entries(schema)) {
if (!(key in params)) {
throw new Error(`Required parameter ${key} is missing`);
}
// Type checking, etc.
}
}
```
## Next Steps
- [Async Workflow Guide](./async-workflows)
- [Error Handling](./error-handling)
- [MCP Server](../integrations/mcp) — generate PDFs from Claude / Cursor / VS Code
- [n8n Integration](../integrations/n8n) — no-code workflow integration
---
# docs/guides/webhooks.md
# Webhook Notifications
You can receive automatic webhook notifications when PDF generation is complete. This eliminates the need for polling and enables real-time detection of completion, allowing you to automate workflows such as email sending or downstream pipelines.
## Overview
Webhook notifications are sent for all PDF generation endpoints:
- `POST /file/sync/single` — synchronous single file
- `POST /file/sync/multiple` — synchronous multiple files
- `POST /file/async/single` — asynchronous single file
- `POST /file/async/multiple` — asynchronous multiple files
## Setting up the webhook URL
1. Open **Workspace Settings**
2. Go to the **Developer** tab
3. Enter the HTTPS URL that should receive notifications under **Webhook URL**
4. Click **Save**
## Webhook payload
When PDF generation finishes, the following payload is POSTed to the configured URL.
### Payload format
```json
{
"event": "file.completed",
"timestamp": "2026-02-15T10:30:45.123Z",
"workspaceId": "ws_abc123",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"designId": "design_123",
"version": 5,
"files": [
{
"fileId": "7f3d1a2b-4c5e-6f7a-8b9c-0d1e2f3a4b5c",
"fileName": "invoice.pdf",
"passthrough": { "pageId": "abc123" },
"share": {
"shareType": "workspace",
"url": "https://app.re-port-flow.com/file/{requestId}/{fileId}",
"passcodeEnabled": false
}
}
]
}
```
### Field descriptions
| Field | Type | Description |
|-------|------|-------------|
| `event` | string | Fixed value: `"file.completed"` |
| `timestamp` | string | Event time, ISO 8601 |
| `workspaceId` | string | Workspace ID |
| `requestId` | string (UUID) | Generation request ID. Use it with the download endpoint |
| `designId` | string | Design ID |
| `version` | number | Design version number |
| `files` | array | Generated file entries |
| `files[].fileId` | string | File ID (per-file download endpoint) |
| `files[].fileName` | string | File name (with extension) |
| `files[].passthrough` | object | The value supplied as `passthrough` on the request (only present when set) |
| `files[].share.shareType` | string | Share type (`workspace` / `invited` / `public`) |
| `files[].share.url` | string | Sharable URL for the file |
| `files[].share.passcodeEnabled` | boolean | Whether passcode protection is enabled |
| `files[].share.passcode` | string | Server-generated passcode (only when `passcodeEnabled=true` AND immediately after generation) |
:::info When to use `passthrough`
ReportFlow does **not** echo back `params` (the data used to render the
PDF) on responses or webhooks, both for payload size and to avoid leaking
business data to webhook endpoints.
If you need to know which business record a PDF corresponds to, put your
own DB id (or any opaque token) into `passthrough` on the request. The
exact value comes back on the response and the webhook unchanged.
```json
{
"fileName": "invoice.pdf",
"passthrough": { "invoiceId": "INV-001", "tenantId": "acme" },
"params": { "customerName": "John Doe", "amount": 10000 }
}
```
When the webhook arrives, look up your DB by `invoiceId` to find the
record to update. `params` (the customer name, amount, etc.) is never
sent off-server.
:::
## Downloading the PDF
Use the values from the webhook payload with the download endpoint:
- ZIP for the whole request: `GET /v1/file/download/{requestId}`
- Individual file: `GET /v1/file/download/{requestId}/{fileId}`
See [File Download](./endpoints/download.md) for the response format and authentication.
## Implementation examples
### Node.js (Express)
```javascript
import express from 'express';
import crypto from 'crypto';
const app = express();
// Keep the raw body so the HMAC signature can be verified
app.use(express.raw({ type: 'application/json' }));
const SECRET = process.env.REPORT_FLOW_WEBHOOK_SECRET;
app.post('/webhook', (req, res) => {
const sigHeader = req.header('X-Report-Flow-Signature') || '';
const parts = Object.fromEntries(
sigHeader
.split(',')
.map((kv) => kv.split('='))
.filter(([, v]) => v !== undefined),
);
const timestamp = parts.t;
const signature = parts.v1;
if (!timestamp || !signature) {
return res.status(400).send('Missing signature components');
}
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(400).send('Stale timestamp');
}
const expected = crypto
.createHmac('sha256', SECRET)
.update(`${timestamp}.${req.body.toString()}`)
.digest('hex');
const expectedBuf = Buffer.from(expected);
const signatureBuf = Buffer.from(signature);
if (
expectedBuf.length !== signatureBuf.length ||
!crypto.timingSafeEqual(expectedBuf, signatureBuf)
) {
return res.status(400).send('Invalid signature');
}
const payload = JSON.parse(req.body.toString());
console.log(`Verified webhook: ${payload.files.length} file(s) ready`);
// Download via /v1/file/download/{requestId}/{fileId} as needed
res.status(200).send('OK');
});
app.listen(3000);
```
### Python (Flask)
```python
import hmac, hashlib, os, time, json
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ['REPORT_FLOW_WEBHOOK_SECRET']
@app.post('/webhook')
def webhook():
raw = request.get_data() # raw bytes; do NOT decode
sig_header = request.headers.get('X-Report-Flow-Signature', '')
parts = dict(
kv.split('=', 1) for kv in sig_header.split(',') if '=' in kv
)
timestamp, signature = parts.get('t'), parts.get('v1')
if not timestamp or not signature:
abort(400, 'Missing signature components')
if abs(time.time() - int(timestamp)) > 300:
abort(400, 'Stale timestamp')
signed_payload = f'{timestamp}.'.encode() + raw
expected = hmac.new(SECRET.encode(), signed_payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(400, 'Invalid signature')
payload = json.loads(raw)
print(f"Verified webhook: {len(payload['files'])} file(s) ready")
return 'OK', 200
```
### Go (net/http)
```go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
var secret = os.Getenv("REPORT_FLOW_WEBHOOK_SECRET")
func handler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
parts := map[string]string{}
for _, kv := range strings.Split(r.Header.Get("X-Report-Flow-Signature"), ",") {
if i := strings.Index(kv, "="); i > 0 {
parts[kv[:i]] = kv[i+1:]
}
}
if parts["t"] == "" || parts["v1"] == "" {
http.Error(w, "missing signature components", 400)
return
}
ts, err := strconv.ParseInt(parts["t"], 10, 64)
if err != nil {
http.Error(w, "invalid timestamp", 400)
return
}
if diff := time.Now().Unix() - ts; diff > 300 || diff < -300 {
http.Error(w, "stale timestamp", 400)
return
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(parts["t"] + "."))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
// hmac.Equal returns false safely on length mismatch
if !hmac.Equal([]byte(expected), []byte(parts["v1"])) {
http.Error(w, "invalid signature", 400)
return
}
w.WriteHeader(200)
}
```
## Security best practices
### 1. Use HTTPS URLs
Webhook URLs must use HTTPS. HTTP URLs are rejected.
### 2. Verify the HMAC-SHA256 signature
ReportFlow signs every webhook payload with HMAC-SHA256. Verifying the signature on your side prevents spoofed requests from third parties.
#### Getting the signing secret (`whsec_...`)
Each workspace has its own webhook signing secret.
- **From the UI**: Workspace Settings > Developer tab
- **API (regenerate)**: `POST /workspace/:workspaceId/webhook/secret/regenerate`
- **API (read)**: `GET /workspace/:workspaceId/webhook/secret`
Regenerating immediately invalidates the previous secret.
#### Signature header format
```
X-Report-Flow-Signature: t=1739610645,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```
- `t`: Unix timestamp (seconds). To prevent replay, recipients should reject timestamps that differ from `now` by more than 5 minutes.
- `v1`: HMAC-SHA256 hex digest. The signed string is `.` (the timestamp, a literal `.`, and the raw request body).
> **Important**: The signed input is the **raw request body before any JSON parsing**. Re-serializing after `JSON.parse` typically changes key order and whitespace, which breaks signature verification.
#### Backward compatibility
Workspaces that have **not** generated a webhook secret will not receive an `X-Report-Flow-Signature` header. To enable signing, generate a secret in the workspace settings first.
### 3. Respond quickly
Endpoints should return within 5 seconds. Defer heavy work (email, DB writes, etc.) to a background queue.
### 4. Don't put credentials in the URL
Do not put authentication tokens or secrets in the webhook URL's query string. Use a header or out-of-band configuration.
## Retry behaviour
ReportFlow retries failed deliveries based on the response status:
- **200-299**: success (no retry)
- **400-499**: client error (no retry)
- **500-599**: server error (**retry**)
Retries happen up to 3 times. Even if all retries fail, PDF generation itself is still considered successful.
## Troubleshooting
### Webhooks aren't arriving
1. **Is the Webhook URL set?** Check Workspace Settings > Developer tab.
2. **Is it HTTPS?** Plain HTTP URLs are rejected.
3. **Is it publicly reachable?** `localhost` and private IPs are blocked. Use [webhook.site](https://webhook.site) for one-off testing.
4. **Does the endpoint return 200?** Non-2xx responses trigger retry. Check your server logs.
If it still doesn't arrive, please contact support.
## Next steps
- [Async Workflows](./async-workflows.md) — bulk generation patterns that rely on webhooks
- [Error Handling](./error-handling.md) — how to handle errors
- [File Download](./endpoints/download.md) — downloading the generated files
---
# docs/integrations/mcp.mdx
import Head from '@docusaurus/Head';
# MCP Server (Claude / Cursor / VS Code)
We provide an official [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server, `reportflow-mcp`, that lets any MCP-compatible AI client — Claude Desktop, Claude Code, Cursor, VS Code, and more — generate Report Flow PDFs from natural language.
- Hosted endpoint (no npm required): `https://mcp.re-port-flow.com/mcp`
- npm: [`reportflow-mcp`](https://www.npmjs.com/package/reportflow-mcp)
- Source: [github.com/re-port-flow/reportflow-mcp](https://github.com/re-port-flow/reportflow-mcp)
- MCP Registry: [`io.github.re-port-flow/reportflow-mcp`](https://registry.modelcontextprotocol.io/v0/servers?search=reportflow)
## Why Report Flow MCP?
Several MCP servers exist for business-document generation, but Report Flow MCP differentiates itself on three axes.
1. **Remote-ready, setup-free** — A hosted endpoint at `https://mcp.re-port-flow.com/mcp` lets you connect from Claude.ai (web) without installing npm or Node.js. Unlike other Japanese 帳票 (chohyo) MCP servers that ship as stdio-only local processes, business users without a dev environment can start using it day one.
2. **No-code template design + template marketplace** — Templates are designed in a browser-based GUI editor (Konva-based) with no code. A free [template gallery](https://templates.re-port-flow.com) covers invoices, quotes, receipts, delivery slips, reports and more, so AI agents can invoke ready-made templates from day one. You don't need to author JSON schemas to add templates.
3. **OAuth 2.0 + dynamic client registration** — Authentication is OAuth 2.0 (Authorization Code + PKCE) with Dynamic Client Registration — no API key is ever handed to the AI client. Local-mode tokens live in the OS keychain; remote-mode tokens are managed server-side. The security posture matches Claude.ai's first-party Connectors.
## What it does
- Generate PDFs from natural-language requests like *"create an invoice for Acme Corp totalling $330"*
- Expose your Report Flow designs and their parameter schemas to the AI as MCP **Resources**
- Bulk-generate many PDFs and download them as a single ZIP
- Save outputs to whichever workspace folder the AI client is currently in
## Requirements
- Node.js 18+ (auto-fetched by `npx`)
- A local environment with a browser (only for the first login)
- A [Report Flow](https://re-port-flow.com) account
These are the requirements for **local execution (npx)**. With the **remote (hosted) server**, you don't need Node.js or a local browser/keychain — all you need is a Report Flow account and an AI client that supports remote MCP servers.
## Setup
There are two ways to set up. **Local execution (npx) is recommended**, and it can save generated PDFs into your local workspace folder. If you'd rather not install Node.js, or you're using a browser-based client such as Claude.ai (web), use the **remote (hosted) server**.
### Local execution (npx, recommended)
#### Claude Desktop / Claude Code / Cursor
Add the following to your config file (`.mcp.json`, `claude_desktop_config.json`, `~/.cursor/mcp.json`, etc.):
```json
{
"mcpServers": {
"reportflow": {
"command": "npx",
"args": ["-y", "reportflow-mcp"]
}
}
}
```
That's the entire setup. No env vars, no API keys, no secrets to manage on the client — authentication uses OAuth.
#### VS Code (MCP-enabled builds)
VS Code uses a top-level `servers` key (different from Claude / Cursor's `mcpServers`). Add the following to `.vscode/mcp.json`:
```json
{
"servers": {
"reportflow": {
"command": "npx",
"args": ["-y", "reportflow-mcp"]
}
}
}
```
### Remote (hosted) server
No npm or Node.js installation required — you connect to a hosted endpoint by URL. This suits Claude.ai (web) and any client that supports remote MCP servers. Authentication runs as OAuth (Dynamic Client Registration) in the client's browser, and tokens are managed server-side, so no local keychain is used.
Endpoint:
```
https://mcp.re-port-flow.com/mcp
```
For clients that support the HTTP transport (Claude Code, Cursor, etc.), add the following to your config file:
```json
{
"mcpServers": {
"reportflow": {
"type": "http",
"url": "https://mcp.re-port-flow.com/mcp"
}
}
}
```
In VS Code (`.vscode/mcp.json`), the top-level key is `servers`:
```json
{
"servers": {
"reportflow": {
"type": "http",
"url": "https://mcp.re-port-flow.com/mcp"
}
}
}
```
With the Claude Code CLI you can also add it with:
```bash
claude mcp add --transport http reportflow https://mcp.re-port-flow.com/mcp
```
In Claude.ai (web / desktop), register the URL above under Settings → **Connectors (custom connector)**. An OAuth consent screen opens in your browser on connect.
:::note Difference from local execution
The remote server can't access your file system, so generated PDFs are not saved to a local workspace folder — they're returned as download URLs instead. If you want generated files saved locally and automatically, use local execution (npx).
:::
## First-run authentication
After reloading the MCP client, ask the AI:
> Authenticate with ReportFlow
A browser window opens. **Sign in → pick a workspace → consent**, and you're done. Tokens are stored in your OS keychain (macOS Keychain / Windows Credential Manager / Linux libsecret) and refreshed automatically.
:::tip When the keychain isn't available
On Linux without libsecret, the server automatically falls back to a chmod-0600 file under `$XDG_STATE_HOME/reportflow-mcp/`.
:::
:::note For the remote (hosted) server
The steps above apply to local execution (npx). With the remote server, the OAuth consent flow starts automatically when the client connects, and tokens are managed server-side. There's no need to ask the AI to "log in" and no local keychain is involved.
:::
## Usage
### 1. Generate a PDF from natural language
```
Using the invoice template, create a PDF for Acme Corp totalling $330.
```
Behind the scenes the AI will:
1. Look up your designs via `list_templates`
2. Fetch the parameter schema with `get_design_parameters`
3. Build a `params` object from your request
4. Call `generate_pdf_sync` and return the local file path
### 2. Slash commands (prompt templates)
| Command | Purpose |
|---|---|
| `/generate_pdf` | Step-by-step recipe for a single PDF |
| `/generate_pdfs` | Recipe for batch PDF generation |
| `/reportflow_help` | Quick feature tour |
### 3. Where files are saved
Output location is resolved in this order:
1. An explicit instruction from the user (e.g. *"save to my Desktop"*)
2. The currently-open workspace root (Claude Code / Cursor / VS Code)
3. The OS temp directory as fallback
## Reference
### Tools (called by the AI)
| Tool | Purpose |
|---|---|
| `authenticate` | First-time / re-authentication |
| `list_templates` | List available designs |
| `get_design_parameters` | Fetch the parameter schema for a design |
| `generate_pdf_sync` / `generate_pdf_async` | Generate one PDF (sync returns a path; async returns a request ID) |
| `generate_pdfs_sync` / `generate_pdfs_async` | Generate many PDFs (returns a ZIP) |
| `download_file` / `download_zip` | Download artifacts produced by async tools |
| `suggest_params` | Translate a natural-language brief into a `params` JSON via MCP Sampling (requires a Sampling-capable client) |
### Resources (attachable as AI context)
| URI | Contents |
|---|---|
| `reportflow://designs` | List of available designs |
| `reportflow://designs/{designId}/parameters` | Parameter schema for one design |
| `reportflow://errors` | Catalog of error messages from the Content Service |
| `reportflow://server-info` | Server feature overview |
### Prompts
`/generate_pdf`, `/generate_pdfs`, `/reportflow_help` — pass arguments and the AI follows the prepared workflow.
## Related endpoints
The MCP server calls the Report Flow API under the hood. For the underlying contracts, see:
- [Single PDF Sync Generation](../guides/endpoints/sync-single.md)
- [Single PDF Async Generation](../guides/endpoints/async-single.md)
- [Multiple PDF Sync Generation](../guides/endpoints/sync-multiple.md)
- [Multiple PDF Async Generation](../guides/endpoints/async-multiple.md)
- [File Download](../guides/endpoints/download.md)
- [Design Parameters](../guides/endpoints/design-parameters.md)
## Troubleshooting
### Errors containing `re-authentication required`
Your tokens have expired. Ask the AI: *"re-authenticate with ReportFlow"*.
### `npx` cannot find the package
```bash
npm cache clean --force
```
then retry.
### No keychain available on Linux
If libsecret is missing, the server automatically falls back to a chmod-0600 file under `$XDG_STATE_HOME/reportflow-mcp/`.
### Browser cannot open over SSH / remote shell
Authenticate **once on a local machine**. After the initial login, the cached token can be reused on remote hosts — either via the same OS keychain entry, or by copying the fallback file mentioned above.
### `Rate limit exceeded` (429)
Per-workspace rate limit. Wait the number of seconds reported in the `Retry-After` header before retrying. For batch jobs, prefer the async endpoints.
## Support
- Bugs / feature requests: [GitHub Issues](https://github.com/re-port-flow/reportflow-mcp/issues)
- General API questions: [Report Flow API documentation](../intro.md)
## Next steps
- [Async Workflows](../guides/async-workflows.md) — bulk-generation best practices
- [Webhook Notifications](../guides/webhooks.md) — completion events and HMAC-SHA256 verification
- [n8n integration](./n8n.md) — low-code workflow integration
---
# docs/integrations/n8n.md
# n8n Integration
We provide an official community node, `n8n-nodes-reportflow`, that lets you call the Report Flow API from any [n8n](https://n8n.partnerlinks.io/6ts9dtojxmtx) workflow. Wire up flows like "form submission → PDF generation → email delivery" without writing code.
- npm: [`n8n-nodes-reportflow`](https://www.npmjs.com/package/n8n-nodes-reportflow)
- Source: [github.com/re-port-flow/n8n-nodes-reportflow](https://github.com/re-port-flow/n8n-nodes-reportflow)
## Installation
In n8n, open **Settings → Community Nodes → Install** and enter the package name:
```
n8n-nodes-reportflow
```
For details, see the [n8n Community Nodes installation guide](https://docs.n8n.io/integrations/community-nodes/installation/).
:::info Self-hosted n8n
On self-hosted instances, `N8N_COMMUNITY_PACKAGES_ENABLED=true` must be set. If you run n8n via Docker Compose, add the env var and restart the container.
:::
After installation, **ReportFlow** appears in the node palette.
## Creating credentials
The ReportFlow node uses **AppKey authentication**.
1. Sign in to Report Flow and open **Workspace Settings → API Keys**
2. Copy the **Application Key** (`ak_xxxxxxxxxxxxxxxx` format)
3. In n8n, create **Credentials → New → ReportFlow AppKey API**
4. Paste the key into **App Key** and save
## Operations
### PDF resource
| Operation | Sync/Async | Description |
|-----------|------------|-------------|
| **Generate (Sync)** | sync | Generate a single PDF and return the binary inline |
| **Generate (Async)** | async | Kick off a single PDF generation. Returns `requestId` and a download URL (combine with webhooks) |
| **Generate Multiple (Sync)** | sync | Generate multiple PDFs in one call and receive a ZIP |
| **Generate Multiple (Async)** | async | Generate multiple PDFs asynchronously |
| **Download** | - | Download a previously generated file by `requestId` / `fileId` |
### Design resource
| Operation | Description |
|-----------|-------------|
| **Get Parameters** | Retrieve the parameter structure expected by a design template |
For endpoint and parameter details, see the API reference:
- [Single PDF Sync Generation](../guides/endpoints/sync-single.md)
- [Single PDF Async Generation](../guides/endpoints/async-single.md)
- [Multiple PDF Sync Generation](../guides/endpoints/sync-multiple.md)
- [Multiple PDF Async Generation](../guides/endpoints/async-multiple.md)
- [File Download](../guides/endpoints/download.md)
- [Design Parameters](../guides/endpoints/design-parameters.md)
## Usage
### 1. Inspect the parameter structure
Before generating, it helps to know what parameters your design expects.
1. Add a **ReportFlow** node
2. Set **Resource** = `Design`, **Operation** = `Get Parameters`
3. Enter the **Design ID** (UUID copied from the Report Flow dashboard)
4. Run — you'll see something like `{ "customerName": "string", "amount": "number", ... }`
### 2. Generate a single PDF (sync)
The most basic flow.
1. Add a **ReportFlow** node
2. Set **Resource** = `PDF`, **Operation** = `Generate (Sync)`
3. Configure:
- **Design ID**: the design's UUID
- **Version**: integer version number
- **File Name**: e.g. `invoice.pdf`
- **Parameters**: JSON; mapping from a previous step like `{{ $json.params }}` works well
4. Pass the binary property (default `data`) to downstream nodes (Email, Slack, Drive, …)
:::info Filename validation
`/ \ : * ? " < > |` and control characters are not allowed. See the [error handling guide](../guides/error-handling.md) for details.
:::
### 3. Async generation + webhook for completion
For high-volume or multi-second jobs, combine the async endpoint with webhooks.
1. Run **Generate (Async)** in the **ReportFlow** node — `requestId` returns immediately
2. In Report Flow, set **Workspace Settings → Developer → Webhook URL** to your n8n **Webhook** node URL
3. When the PDF is ready, Report Flow POSTs the webhook to n8n
4. Chain the webhook to a **ReportFlow** node with **Download** Operation, passing `requestId` / `fileId`
Use the `passthrough` field to correlate PDFs with your business records — `params` (business data) is **never** included in webhook payloads. See the [webhook guide](../guides/webhooks.md) for details.
### 4. Bulk generation
When generating PDFs from each row of a CSV or database table:
1. Fetch rows with **Spreadsheet File**, **MySQL**, etc.
2. Use **ReportFlow** with **Generate Multiple (Sync)** or **(Async)**
3. Pass an array of `{ fileName, params }` to **Files** (map via n8n's expression editor)
Rate limits are 30 req/min for sync and 100 req/min for async endpoints. Use **Split In Batches** to throttle if needed.
## Troubleshooting
### `認証方式ヘッダーが存在しません` (412)
The server returns this Japanese string ("Authentication header is missing"). The AppKey credential is unlinked, or got reset by a node version upgrade. Re-select the credential and save.
### `認証情報が不正です` (401)
The server returns this Japanese string ("Invalid credentials").
- Re-check the AppKey value (watch for leading/trailing whitespace and newlines)
- If you regenerated the AppKey in the Report Flow admin UI, update the n8n credentials accordingly
### `プラン情報が見つからないため、この操作は許可されていません` (403)
The server returns this Japanese string ("Plan information was not found, so this operation is not permitted"). Either the workspace plan information is unavailable, or you've hit the monthly file creation cap. In the latter case the message is `今月のファイル作成回数上限(X)に達しました。プランをアップグレードするか、翌月までお待ちください。` ("This month's file creation limit (X) has been reached. Please upgrade the plan or wait until next month."). Upgrade the plan from the admin console.
### `Rate limit exceeded` (429)
Per-workspace rate limit. Wait the number of seconds reported in the `Retry-After` header before retrying. For batch jobs, prefer the async endpoints and split with **Split In Batches**.
### Webhooks aren't arriving
1. The webhook URL must be HTTPS (`localhost` and HTTP are rejected)
2. If you self-host n8n, make sure it's reachable from the public internet (ngrok, Cloudflare Tunnel, …)
3. Switch the n8n Webhook node to **Production URL** before saving (the Test URL only fires while the workflow is being edited)
See the [Webhook guide](../guides/webhooks.md) for more.
## Support
- Bugs / feature requests: [GitHub Issues](https://github.com/re-port-flow/n8n-nodes-reportflow/issues)
- General API questions: [Report Flow API documentation](../intro.md)
## Next steps
- [Async Workflows](../guides/async-workflows.md) — bulk-generation best practices
- [Webhook Notifications](../guides/webhooks.md) — completion events and HMAC-SHA256 verification
---
# docs/intro.md
# Report Flow API - Getting Started
Welcome to the Report Flow API! This guide will help you generate PDFs in 5 minutes.
## ⚡ 5-Minute Quick Start
:::tip Don't have an account yet?
You need a Report Flow account to get an API key. [Sign up for free](https://re-port-flow.com/register) before you begin.
:::
### Step 1: Get API Keys (1 minute)
Access the Report Flow admin panel and get the following information:
1. Navigate to **Workspace Settings** → **API Keys**
2. Copy the **Application Key** (`appkey`) — used as the `appkey` HTTP header.
:::tip
Authentication uses a single **`appkey`** header (lowercase). Store the value securely.
:::
### Step 2: Check Design ID (1 minute)
1. Go to **Design Management**
2. Select the design you want to use
3. Copy the **Design ID** (e.g., `550e8400-e29b-41d4-a716-446655440000`)
### Step 2.5: Check Parameter Structure (1 minute, Optional)
Check the parameter structure required by the design:
```bash
curl -X GET https://api.re-port-flow.com/v1/file/design/parameter/550e8400-e29b-41d4-a716-446655440000 \
-H "appkey: your-app-key"
```
Response example:
```json
{
"customerName": "string",
"invoiceNumber": "string",
"amount": "number"
}
```
:::tip
For details on parameter structure, see the [Design Parameters Guide](./guides/endpoints/design-parameters.md).
:::
### Step 3: Generate Your First PDF (3 minutes)
Run the following cURL command in your terminal:
```bash
curl -X POST https://api.re-port-flow.com/v1/file/sync/single \
-H "Content-Type: application/json" \
-H "appkey: your-app-key" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"params": {
"customerName": "John Doe",
"invoiceNumber": "INV-2024-001",
"amount": 10000
}
}
}' \
--output my-first-pdf.pdf
```
:::success
On success, `my-first-pdf.pdf` will be saved to the current directory!
:::
## 🎯 Main Use Cases
### Use Case 1: Instant Invoice Generation
Generate and download PDFs instantly when users click a button.
**Endpoint**: [Sync Single PDF Generation](./guides/endpoints/sync-single.md)
```javascript
// On button click
async function downloadInvoice(invoiceId) {
const pdf = await generatePDF({
designId: 'invoice-template',
version: 1,
fileName: `invoice_${invoiceId}.pdf`,
data: invoiceData
});
// Download in browser
downloadFile(pdf, `invoice_${invoiceId}.pdf`);
}
```
### Use Case 2: Bulk Monthly Report Generation
Generate reports for all customers at month-end and send via email.
**Endpoint**: [Async Multiple PDF Generation](./guides/endpoints/async-multiple.md)
```javascript
// Batch processing
async function generateMonthlyReports(customers) {
const contents = customers.map(customer => ({
fileName: `report_${customer.id}.pdf`,
params: customer.data
}));
const result = await generateMultiplePDFsAsync('report-template', contents);
// Send emails to each customer
await sendEmailsWithPDFs(result);
}
```
### Use Case 3: Preview Feature
Real-time preview in design editor.
**Endpoint**: [Sync Single PDF Generation](./guides/endpoints/sync-single.md)
```javascript
// On preview button click
async function showPreview(designId, params) {
const pdf = await generatePDF({
designId,
version: 1,
fileName: 'preview.pdf',
data: params
});
// Display in browser
openPDFInNewTab(pdf);
}
```
## 🧭 API Overview
### PDF Generation Endpoints
| Endpoint | Purpose | Timeout | Response |
|----------|---------|---------|----------|
| [POST /file/sync/single](./guides/endpoints/sync-single.md) | Sync single PDF | 120 sec | PDF file |
| [POST /file/async/single](./guides/endpoints/async-single.md) | Async single PDF | None | URL + fileId |
| [POST /file/sync/multiple](./guides/endpoints/sync-multiple.md) | Sync multiple PDFs | 120 sec | ZIP file |
| [POST /file/async/multiple](./guides/endpoints/async-multiple.md) | Async multiple PDFs | None | URL + fileId |
### Other Endpoints
| Endpoint | Description |
|----------|-------------|
| [GET /file/download/\{uuid\}](./guides/endpoints/download.md) | Bulk ZIP download |
| [GET /file/download/\{uuid\}/\{fileId\}](./guides/endpoints/download.md) | Individual file download |
| [GET /file/design/parameter/\{designId\}](./guides/endpoints/design-parameters.md) | Get design parameters |
## 🧪 Try in Postman
If you want to try the API right away, use our official Postman collection:
### Postman Public Workspace
[](https://www.postman.com/mone-pla/reportflow)
The official Postman collection includes:
- ✅ Runnable requests for all 7 endpoints
- ✅ Automatic application-key authentication
- ✅ Realistic sample parameters
- ✅ Auto-test scripts for response validation
- ✅ Error-case samples (412, 401, 400, 429)
## 📚 Next Steps
- [Design Parameters](./guides/endpoints/design-parameters.md) - How to check parameter structure
- [Authentication Details](./authentication/overview)
- [PDF Generation Guide](./guides/pdf-generation)
## 🔧 Limitations
- **Request Size**: Maximum 50MB (Base64-encoded; ~37MB raw equivalent)
- **File Name**: Anything except `/ \\ : * ? " < > |` and control characters is allowed
- **Authentication**: `appkey` header (lowercase) required on every request
## 💡 Sample Code
### JavaScript (Node.js)
```javascript
const axios = require('axios');
async function generatePDF() {
const response = await axios.post(
'https://api.re-port-flow.com/v1/file/sync/single',
{
designId: '550e8400-e29b-41d4-a716-446655440000',
version: 1,
content: {
fileName: 'invoice.pdf',
params: {
customerName: 'John Doe',
invoiceNumber: 'INV-2024-001',
amount: 10000
}
}
},
{
headers: {
'appkey': 'your-app-key'
},
responseType: 'arraybuffer'
}
);
return response.data;
}
```
### Python
```python
import requests
def generate_pdf():
url = 'https://api.re-port-flow.com/v1/file/sync/single'
headers = {
'appkey': 'your-app-key',
'Content-Type': 'application/json'
}
data = {
'designId': '550e8400-e29b-41d4-a716-446655440000',
'version': 1,
'content': {
'fileName': 'invoice.pdf',
'params': {
'customerName': 'John Doe',
'invoiceNumber': 'INV-2024-001',
'amount': 10000
}
}
}
response = requests.post(url, headers=headers, json=data)
return response.content
```
## Integrations
You can call Report Flow without writing API code directly:
- [MCP Server (Claude / Cursor / VS Code)](./integrations/mcp) — generate PDFs from natural language inside AI clients
- [n8n Integration](./integrations/n8n) — no-code workflow automation
## 🆘 Support
For questions or support, please contact:
- Email: support@re-port-flow.com
---
# docs/limitations.md
# API Limitations
This page describes the limitations of the Report Flow Content Service API.
:::tip
These limitations apply to the production environment (`api.re-port-flow.com`).
:::
---
## 📦 Request Size Limit
### Maximum Request Size: 50MB
The maximum request body size is **50MB** (after Base64 encoding).
```text
Effective file size estimate:
- Before Base64 encoding: ~37MB
- Encoding overhead: ~1.33x
```
:::tip
As a document output SaaS, we provide sufficient capacity to handle reports with large amounts of high-resolution images.
:::
### Behavior on Exceeding Limit
When request size exceeds 50MB:
```json
{
"statusCode": 413,
"message": "Payload Too Large",
"error": "Request Entity Too Large"
}
```
### Handling Large Files
For large images or data, consider the following approaches:
1. **Optimize Images**
- Reduce resolution (72-150dpi for web, 300dpi for print)
- Adjust compression (JPEG quality 80-85% recommended)
- Remove unnecessary metadata
2. **Use Async Endpoints**
- For generating many PDFs, use [async endpoints](./guides/endpoints/async-multiple)
- Process large data without timeout concerns
3. **Split Data**
- Divide requests when generating multiple PDFs in a single request
---
## ⏱️ Timeout Limits
### Sync Endpoints: 120 seconds
**Affected Endpoints**:
- `POST /v1/file/sync/single`
- `POST /v1/file/sync/multiple`
A 120-second timeout is configured to handle complex document processing.
**Timeout Response**:
```json
{
"statusCode": 504,
"message": "Gateway Timeout",
"error": "Request timeout"
}
```
**For processing exceeding 120 seconds**:
- Use async endpoints (`/async/single`, `/async/multiple`)
- Reduce design complexity (fewer images, table rows)
- Split requests
### Async Endpoints: No Limit
**Affected Endpoints**:
- `POST /v1/file/async/single`
- `POST /v1/file/async/multiple`
Async endpoints have no timeout limit. They're suitable for bulk PDF generation and complex design processing.
---
## 🚦 Rate Limit
**Rate limiting has been implemented as of February 2024.**
### Workspace-based Limits
Rate limits are applied per workspace. Requests from the same workspace are counted together, without affecting other workspaces.
| Endpoint Type | Limit | Target Endpoints |
|--------------------------|------------------|--------------------------------------|
| **Sync Endpoints** | 30 requests/min | `/sync/single`, `/sync/multiple` |
| **Async Endpoints** | 100 requests/min | `/async/single`, `/async/multiple` |
| **Download & Retrieval** | 100 requests/min | `/download/*`, `/design/parameter/*` |
### Rate Limit Exceeded Response
When the rate limit is exceeded, the following response is returned:
```http
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"
}
```
The response includes a **`Retry-After` header** indicating the integer number of seconds to wait before retrying (per RFC 9110 §10.2.3). Clients should read this header and wait the specified duration rather than applying their own exponential backoff. The number embedded in the message body is the same value in human-readable form.
### Design Rationale
- **Sync Endpoints (30 req/min)**: Considering the 120-second timeout, this allows up to 60 parallel
processes, aligned with ECS task capacity.
- **Async Endpoints (100 req/min)**: Supports batch processing and bulk generation. Protects server
resources while maintaining high throughput.
- **Download (100 req/min)**: Higher limit for lightweight operations.
:::tip
For normal UI operations (approximately 1 request every 2 seconds), you won't reach the limit.
For batch processing, please use async endpoints.
:::
---
## 📝 Filename Restrictions
### Disallowed Characters
The following characters cannot be used in filenames:
| Character | Name |
|-----------|------|
| `/` | Slash |
| `\` | Backslash |
| `:` | Colon |
| `*` | Asterisk |
| `?` | Question mark |
| `"` | Double quote |
| `<` `>` | Angle brackets |
| `\|` | Pipe |
| Control chars | `0x00`–`0x1F` |
All other characters (alphanumeric, Japanese, spaces, various symbols, etc.) are allowed.
### Examples
```json
✅ Valid:
{
"fileName": "invoice_2024-01.pdf",
"fileName": "report-2024-01.pdf",
"fileName": "レポート (January).pdf",
"fileName": "report #001.pdf"
}
❌ Invalid:
{
"fileName": "invoice/2024.pdf", // Slash not allowed
"fileName": "data:2024.pdf", // Colon not allowed
"fileName": "report*.pdf" // Asterisk not allowed
}
```
---
## 🔐 Security Requirements
### HTTPS Required
The production environment only accepts HTTPS connections. HTTP requests are automatically redirected to HTTPS.
```bash
# ✅ Correct
curl -X POST https://api.re-port-flow.com/v1/file/sync/single \
-H "appkey: your-app-key"
# ❌ Incorrect (HTTP not allowed)
curl -X POST http://api.re-port-flow.com/v1/file/sync/single
```
### TLS Version
- **Minimum version**: TLS 1.2
- **Recommended version**: TLS 1.3
- TLS 1.0/1.1 are not supported
### API Endpoint
**Production**:
```text
URL: https://api.re-port-flow.com/v1/*
```
**Staging**:
```text
URL: https://api.stg.re-port-flow.com/v1/*
```
**Authentication Failure**:
```json
{
"statusCode": 401,
"message": "認証情報が不正です",
"error": "Unauthorized"
}
```
(The server returns the message in Japanese; it translates to "Invalid credentials".)
### API Key Rotation
For improved security, we recommend regenerating your application key **every 3 months**.
See [API Key Management](./authentication/api-keys#rotate-periodically) for details.
---
## 📊 Plan Limits
Workspace plans have the following restrictions:
### Design File Limit
Each plan has a maximum number of design files that can be created in a workspace.
### File Generation Limit
Each plan has a monthly limit on PDF generation count.
:::info
Specific limits vary by plan. Check your current usage and limits in the admin dashboard.
:::
### Exceeding Plan Limits
When plan limits are reached:
```json
{
"statusCode": 403,
"message": "プラン情報が見つからないため、この操作は許可されていません",
"error": "Forbidden"
}
```
When the monthly file generation limit is reached, the server returns:
```json
{
"statusCode": 403,
"message": "今月のファイル作成回数上限(X)に達しました。プランをアップグレードするか、翌月までお待ちください。",
"error": "Forbidden"
}
```
(The server returns the messages in Japanese.)
---
## 🌐 Network Restrictions
### CloudFront Requests
External requests are processed via CloudFront:
```text
Client → CloudFront → ALB → ECS (content-service)
```
---
## 📋 Additional Restrictions
### Health Check Endpoint
The `/v1/health/check` endpoint is accessible without authentication. This endpoint is not logged in access logs.
### CORS Configuration
The following headers are allowed:
- `Origin`
- `X-Requested-With`
- `Content-Type`
- `Accept`
- `Authorization`
- `appkey`
Requests from all origins (`*`) are accepted.
---
## 🆘 Handling Limit Exceeded Errors
### Request Size Exceeded
1. Optimize images
2. Split data
3. Use async endpoints
### Timeout
1. Use async endpoints (`/async/single`, `/async/multiple`)
2. Simplify design
3. Split requests
### Plan Limit Exceeded
1. Upgrade plan in admin dashboard
2. Delete unnecessary design files
3. Adjust usage frequency
---
## 📞 Support
For questions or support regarding limitations:
- Email: `support@re-port-flow.com`
- Admin Dashboard: `https://re-port-flow.com/support`
---
## Changelog
| Date | Changes |
|------------|-----------------|
| 2026-02-15 | Initial version |
---
# docs/user-guide/01-getting-started.md
# Getting Started
Welcome to Report Flow. This guide will get a first-time user **from zero to a generated PDF in 30 minutes**, step by step, with the actual screens in front of you.
:::info Who this guide is for
**Anyone** building invoices, statements, or other documents with Report Flow. No programming experience required.
:::
## A 30-second tour first
The clip below shows building an invoice in the Report Flow design editor in about 30 seconds. Watch it once to get a feel for what you'll be building.
## What you'll learn
By the end of this guide you'll be able to design a document and output it as a PDF on your own.
1. **This page**: register an account and sign in to Report Flow
2. [Workspaces and Designs](./create-design): create the place where designs live (a workspace) and your first design
3. [Editor Basics](./editor-basics): how to use the design editor
4. [Adding Objects](./add-objects): place elements on the design
5. [Variables Basics](./variables-basics): swap in values at output time
6. [Save and Output PDF](./output-pdf): finish up and export as PDF
7. [FAQ](./faq)
---
## Step 1: Register an account
Already have an account? Skip ahead to [Step 2](#step-2-sign-in).
1. Open the Report Flow registration page in your browser.
2. Enter your **email address** and **password**.
3. Click **Register**.
4. A **confirmation email** will be sent to the address you entered. Open the link in that email.
5. Once confirmation completes, the page switches to the sign-in screen.
:::tip Password guidance
A mix of upper-case letters, lower-case letters, and digits, 8+ characters long, is a safe baseline.
:::
### ✅ Did it work?
- The confirmation email arrived
- Opening the link in the email shows the "Email confirmed" screen
If both are true, registration is done.
---
## Step 2: Sign in
1. Open the sign-in screen.
2. Enter the **email address** and **password** you registered with.
3. Click **Sign in**.
4. The workspace list screen appears.
### ✅ Did it work?
- The workspace list appears after signing in
If yes, you're ready to continue.
---
## Troubleshooting
| Symptom | What to try |
|---------|-------------|
| Confirmation email never arrived | Check your junk/spam folder. If still missing, you can resend it from the registration page. |
| Forgot your password | Use "Forgot your password?" on the sign-in screen to reset it. |
| Blank page after signing in | Reload the browser (Windows: `Ctrl + R` / Mac: `Cmd + R`). |
| Register button is disabled | Confirm your email contains `@` and that the password field is non-empty. |
---
## Next step
Continue with **[Workspaces and Designs](./create-design)**. You'll set up the container that holds designs (a workspace) and create your first design.
---
# docs/user-guide/02-create-design.md
# Workspaces and Designs
On this page you'll create the container that holds designs — a **workspace** — and create your **first design** inside it.
:::info What is a workspace?
A place where you keep designs (invoices, statements, etc.) together. Most teams set up either one workspace per company or one per department.
:::
---
## Create a workspace
Right after signing in you land on the workspace list. If you don't have a workspace yet, you'll create one here.
1. On the list screen, click the **Create new** card.
2. The workspace creation screen opens.
3. Enter a **workspace name** (e.g. `Accounting` / `Internal use`).
4. Click **Create workspace**.
5. The completion screen appears and you can move on to creating a design.
### ✅ Did it work?
- The screen shows "Workspace created"
- A "Create design" button is visible
---
## Create a design
Once the workspace exists, create your **first design** inside it.
1. On the design list screen, click the **+** (Create new) card in the top-left.
2. The new-design modal opens.
3. Enter a **design name** (e.g. `Invoice template`).
4. Pick a paper size.
- **A4** is the most common choice. When in doubt, pick this one.
5. Click **Create**.
6. The design editor opens.
:::tip You can rename the design later
The design name can be changed at any time. A placeholder name to start with is fine.
:::
### ✅ Did it work?
- A blank, paper-shaped area (the **canvas**) is shown in the center
- Icons for "Text", "Image", "Table", etc. appear on the left
- A save button and a back button are at the top
If all three are true, the editor opened correctly.
---
## Troubleshooting
| Symptom | What to try |
|---------|-------------|
| No "Create new" card | You may already be a member of a workspace. Pick the right one from the list. |
| Cannot pick a paper size | Available paper sizes depend on your plan. Use **A4** for now. |
| Editor is blank with no icons | Reload the browser (Windows: `Ctrl + R` / Mac: `Cmd + R`). If that doesn't help, sign out and back in. |
---
## Next step
The editor is ready. Continue with **[Editor Basics](./editor-basics)** to learn the layout of the screen and the basics of using it.
---
# docs/user-guide/03-editor-basics.md
# Editor Basics
Get familiar with the design editor screen and the basic operations: **zoom, pan, select, undo, and grid**. Knowing these makes everything else go smoothly.
---
## Layout of the screen
The editor has three main areas.
| Area | What it's for |
|------|---------------|
| **Left sidebar** | The toolbox: text, image, table, etc. — the things you can place on the design |
| **Center canvas** | The paper itself. You arrange objects here |
| **Right properties panel** | Settings (font size, color, …) for whichever object you have selected |
---
## Zoom
Use zoom when you want to inspect detail or take in the whole page.
| To do this | Use this |
|------------|----------|
| Zoom in | **`Ctrl` + scroll wheel up** (Mac: `Cmd` + wheel) or click the **`+` button on the bottom toolbar** |
| Zoom out | **`Ctrl` + scroll wheel down** (Mac: `Cmd` + wheel) or click the **`-` button on the bottom toolbar** |
| Fit to screen | Click the zoom indicator at the bottom (e.g. `100%`) and pick **Fit** |
:::tip How to use it
- If you'd rather not use the keyboard, the bottom toolbar's `+` / `-` buttons let you zoom with the mouse only. The current zoom level (e.g. `100%`) is shown in the same place.
- For precise alignment, zoom to **150–200%**. To check overall balance, zoom out to **50–75%**.
:::
---
## Pan the canvas
When you're zoomed in and only part of the page is visible, panning lets you move around without dragging objects by accident.
- **Hold `Space` and drag** to move the canvas.
- Plain mouse-drag would move whichever object you grab, so use `Space` for panning.
---
## Select objects
To edit a piece of text or an image you've placed, you first **select** it.
| To do this | Use this |
|------------|----------|
| Select one | **Click** on the canvas, or click an item (e.g. `text1`) in the **Object list** on the right |
| Select multiple | Drag a rectangle starting on empty space |
| Add one to the selection | Hold `Shift` and click |
| Clear the selection | Click empty space on the canvas, or press `Esc` |
The selected object is highlighted with a blue outline and corner handles. While selected, you can change its settings in the right-hand properties panel.
:::tip The Object list is a reliable way to select
If an object is small or overlaps with another and is hard to click, use the **Object list** tab on the right side to pick it directly. Every object placed on the design is listed there with a name like `text1` or `image1`.
:::
---
## Undo / Redo
You can take back a mistake easily.
| To do this | Use this |
|------------|----------|
| Undo | `Ctrl + Z` (Mac: `Cmd + Z`) or the **undo arrow (↶)** in the bottom toolbar |
| Redo | `Ctrl + Shift + Z` (Mac: `Cmd + Shift + Z`) or the **redo arrow (↷)** in the bottom toolbar |
:::tip Toolbar buttons for undo/redo
The bottom toolbar has **undo (↶) / redo (↷)** buttons. They work with a single click, no shortcut required. The buttons are dimmed when there's nothing left to undo or redo.
:::
---
## Show the grid
A **grid** helps when you want objects to line up neatly. All grid settings live on the bottom toolbar.
1. Click the **grid icon** (a small lattice) in the bottom toolbar.
2. The menu lets you toggle:
- **Show grid**: turn `ON` to display the grid on the canvas.
- **Snap to grid**: turn `ON` so dragging an object snaps it to the nearest grid line.
- **Size**: change the grid spacing in pixels (1–100).
:::tip Easy alignment
If you want objects perfectly lined up, set both **Show grid: ON** and **Snap to grid: ON**. Drag operations will snap to the nearest grid line, making clean layouts much easier.
:::
---
## About auto-save
The editor **auto-saves** while you work. You don't have to hit Save every minute — your changes are pushed to the server after a short delay.
That said, at meaningful checkpoints it's a good idea to click the **Save** button in the top toolbar to be sure.
---
## ✅ What you should be able to do by now
- Zoom in/out with `Ctrl` + scroll wheel or the bottom toolbar `+` / `-`
- Pan the canvas by holding `Space` and dragging
- Select an object by clicking it or picking it from the Object list on the right
- Undo with `Ctrl + Z` / `Cmd + Z` or the bottom toolbar's undo arrow
- Toggle Show grid / Snap to grid from the bottom toolbar's grid icon
If all of those feel natural, you're ready to keep going.
---
## Next step
With the basics in hand, continue with **[Adding Objects](./add-objects)**. You'll start placing real content on the design.
---
# docs/user-guide/04-add-objects.md
# Adding Objects
This page covers **every kind of object** you can place on a design, with a short demo video for each section.
:::info What's an "object"?
We call any element you place on the design an **object** — text, image, table, shape, etc. are all objects.
:::
---
## Object types
| Type | What it's for |
|------|---------------|
| [**Text**](#add-text) | Headings, body copy, captions |
| [**Rectangle / Ellipse**](#add-shapes-rectangle--ellipse) | Borders, dividers, accents |
| [**Image**](#add-an-image) | Logos, photos, hanko (seal) images |
| [**QR code / Barcode**](#add-qr-code--barcode) | Scannable URLs and product codes |
| [**Table**](#add-a-table) | Line items, list-style data |
| [**Header / Footer**](#add-headers--footers) | Page-top and page-bottom content |
| [**Group**](#group-objects) | Treat several objects as one |
Click a link to jump straight to that section.
---
## Add text
Place text — item names, totals, or any copy.
1. Pick the **Text** icon from the toolbar.
2. **Drag a frame** on the canvas where you want the text.
3. Type into the input field that appears.
4. Click **Confirm**.
To edit existing text, **double-click** it. Use the properties panel to change **font size, color, and font face**.
---
## Add shapes (rectangle / ellipse)
Place rectangles or ellipses for borders, dividers, or accents.
1. Open the **shape tool** in the toolbar.
2. Pick **Rectangle** or **Ellipse**. Clicking the shape tool itself selects the current shape; use the dropdown arrow to switch shapes.
3. Drag on the canvas to set its size.
4. After placing, use the properties panel to adjust **fill color, stroke width, corner radius**, etc.
---
## Add an image
Place an image file — your company logo, a hanko (seal), etc.
1. Open the **image tool** from the toolbar.
2. The **"Pick an image"** dialog opens.
3. Use the **Upload** tab to add a new file, or the **Library** tab to pick a previously uploaded image.
- Supported formats: PNG / JPEG / GIF / SVG / WebP
4. Click **Set image**.
5. The dialog closes and the editor enters **image placement mode**.
6. Drag on the canvas to set the image size.
:::tip Reusing logos and recommended sizes
- Once uploaded, an image can be reused in any design within the same workspace (Library tab).
- There's no strict size cap, but keeping images **under a few MB** keeps display and PDF generation snappy. Consider compressing very high-resolution photos before uploading.
:::
---
## Add QR code / barcode
Place a scannable code.
1. Open the **image tool** dropdown in the toolbar.
2. Pick **QR code** or **Barcode**.
3. Drag on the canvas to place it.
4. In the properties panel on the right, fill in the **Value** — the data you want to encode (URL, product code, etc.).
5. The code on the canvas updates as you type.
:::tip Per-output values
The QR/barcode **Value** field can also be a **variable**. For example, to encode a different URL per customer, use a parameter you define in **[Variables Basics](./variables-basics)** (next page) as the Value.
:::
---
## Add a table
Place rows of variable-count data — line items, lists, etc.
1. Pick the **Table** tool in the toolbar.
2. Drag on the canvas to set its size.
3. The **Table settings** dialog opens.
4. Pick an **array parameter** (you need an array-typed parameter prepared in advance — see [Variables Basics](./variables-basics)).
5. Add or edit **columns** as needed.
6. Click **Confirm** to place the table.
---
## Add headers / footers
Show titles or notes at the top and bottom of the page.
1. Place the header content as **Text** at the top of the page.
2. Place the footer content as **Text** at the bottom of the page the same way.
3. Combine images for logos, or variables for things like page numbers.
:::info About dedicated header/footer regions
The current version recommends placing headers and footers as ordinary text/image objects on the page. Dedicated, page-spanning header/footer regions are planned for a future release.
:::
---
## Group objects
Treat related objects as one.
1. **Select** the objects you want to group by dragging a rectangle around them.
2. Click the **Group** button on the toolbar.
3. From now on, the group can be **moved, resized, or copied** as a single unit.
---
## ✅ Page completion check
- You placed at least one of each: text, shape, image, QR/barcode, table
- You can click a placed object to select it and change its properties
- You can drag-select multiple objects and group them
---
## Next step
Once you can place objects, continue with **[Variables Basics](./variables-basics)** to make your design accept different values at output time.
---
# docs/user-guide/05-variables-basics.md
# Variables Basics (Making Values Dynamic)
Some values change every time you output — the **recipient name** or **total amount** on an invoice, for example. In Report Flow you first define a **parameter**, then **embed it in text**, so the value can be filled in at output time.
:::info What you'll learn here
- The difference between a "fixed value" and a "variable (parameter)"
- How to define a parameter from the **Parameters** button in the top toolbar
- How to embed a defined parameter into a text object
:::
---
## "Fixed value" vs "variable"
| Kind | Example | Does it change per output? |
|------|---------|----------------------------|
| **Fixed value** | The heading `Invoice` | Stays the same |
| **Variable (parameter)** | The customer's name (e.g. `John Doe`) | Different each output |
Fixed values are typed into text directly. For variables, you first define a "slot" (a parameter) that says **"a different value goes here every time"**, and then you embed it in text.
---
## Step 1: Define a parameter
Register the values that should change per output as **parameters**.
1. In the top toolbar, click **Parameters**.
2. The parameter dialog opens.
3. In the left panel, **Add parameter** → **Text** adds a new text-typed parameter.
4. Rename the **parameter name** to something memorable (e.g. `customerName`).
5. Set a **value** — a sample used while editing (e.g. `John Doe`).
6. Click **Update** to save.
7. When you're done, close the dialog (`Esc` or the × in the top-right).
### Naming tips
| Use case | Recommended name |
|----------|------------------|
| Customer name | `customerName` |
| Invoice date | `invoiceDate` |
| Invoice amount | `amount` |
| Order number | `orderNumber` |
Stick to ASCII letters and digits. Avoid forgettable names like `var1` or `aaa`.
### ✅ Did it work?
- When you re-open the parameter dialog, the parameters you defined are listed there
---
## Step 2: Embed a variable in text
Now embed the parameter you defined into a text object on the canvas.
1. Pick the **Text** tool and drag a frame on the canvas.
2. The **Text edit** modal opens.
3. Switch to the **Variables / Formula** tab at the top.
4. Click `customerName` in the parameter list — `@customerName` is inserted into the input.
5. Click **Confirm**.
6. The text on the canvas shows the sample value you set in Step 1 (e.g. `John Doe`).
### ✅ Did it work?
- The text on the canvas shows the Step 1 sample value
- You can repeat the same steps to embed a different parameter into another text object
---
## A note on formulas (advanced)
To format numbers with thousands separators, or to format dates as `April 25, 2026`, use **formulas**. The "Variables / Formula" tab provides functions like `comma()` and `dateFormat()` that you can combine with variables.
Detailed formula usage isn't covered in this first edition of the guide; we'll add it in a future update.
---
## Next step
Once you can embed variables, continue with **[Save and Output PDF](./output-pdf)** — you'll save the design and generate a PDF, supplying values for the variables along the way.
---
# docs/user-guide/06-output-pdf.md
# Save and Output PDF
This page covers **saving** the design you've been building and **outputting it as a PDF**. At the end you'll also see how to find past outputs in the **output history**.
---
## Step 1: Save the design
In Report Flow your edits **auto-save** as you work — there's no need to save explicitly. But at meaningful checkpoints, click the **Save** button to be sure.
1. Click **Save** in the top toolbar.
2. A "saving…" indicator appears, followed by something like "Saved" / "Up to date".
### ✅ Did it work?
- The area near the Save button shows "Saved" or "Up to date"
---
## Step 2: Output as PDF
Once saved, output a PDF.
1. Click **Output** in the top toolbar.
2. The output dialog opens.
3. If your design uses variables, **fill in the values** here (e.g. `customerName` → `John Doe`). Leaving a field blank uses the parameter's default value.
4. Click **Output** in the dialog.
5. PDF generation starts. When it's done the file is **downloaded** or **previewed**.
:::tip Skipping the value entry
If you set a **default (sample) value** when you defined the parameter, leaving the field blank uses that value.
:::
### ✅ Did it work?
- The output PDF opens or lands in your Downloads folder
- The values you supplied for variables appear in the PDF (the real values, not placeholders)
---
## Step 3: Check the output history
Past outputs are available from the **output history**. You can re-download them, so you don't have to regenerate every time.
1. Pick **Output history** from the left-hand menu (or open it from the design detail screen).
2. From the list, pick the PDF you want to download.
3. Click **Download**.
:::info How long history is kept
Outputs are retained for the period defined by your plan. If you need long-term storage, also keep a local copy after each output.
:::
### ✅ Did it work?
- Your most recent output appears in the history
- You can re-download it from there
---
## 🎉 Congratulations
If you got this far, you've walked through the **golden path** of Report Flow's core features.
| What you achieved |
|-------------------|
| ✅ Created an account and signed in to Report Flow |
| ✅ Created a workspace and a design |
| ✅ Used the basic editor operations |
| ✅ Placed every kind of object — text, images, tables, … |
| ✅ Used variables to inject per-output values |
| ✅ Output a PDF and re-downloaded it from history |
---
## Troubleshooting
| Symptom | What to try |
|---------|-------------|
| Clicking Output does nothing | Check your browser's pop-up blocker — you may need to allow the Report Flow domain. |
| Variable values don't show up in the PDF | Make sure the variable name in the input matches the one in the design **exactly** (case-sensitive). |
| The output isn't in the history | It can take a few seconds to a few minutes to appear. Wait briefly and reload the page. |
---
## Next step
That's the end of the guide proper. For anything else, take a look at the **[FAQ](./faq)**.
---
# docs/user-guide/07-faq.md
# FAQ
Common questions that aren't covered in detail elsewhere in the guide.
---
## Editing
### Q1. When are my edits saved?
**They auto-save.** You don't have to click Save — every edit is pushed to the server after a short delay. At important checkpoints, click **Save** in the top toolbar to be sure.
---
### Q2. I want to reuse the same design for another customer
From the design list, pick **Duplicate** to make a copy of the current design. Rename the copy and use it as a new version.
---
### Q3. I made a mistake and want to undo it
`Ctrl + Z` (Mac: `Cmd + Z`) takes you back one step. Press it repeatedly to go back further. To redo, use `Ctrl + Shift + Z` (Mac: `Cmd + Shift + Z`).
---
## Variables and merging
### Q4. I want commas every three digits in money amounts
Use the **formula** functions for number formatting — `comma()` produces output like `1,234,567`. The same mechanism formats dates (e.g. `April 25, 2026`).
Detailed formula usage isn't covered in this first edition of the guide; we'll add it in a future update.
---
### Q5. I want a table whose row count varies per output (line items)
That's supported. On a **table**, configure it to **inject line-item data** — the row count adjusts automatically to the number of records.
The detailed table configuration isn't covered in this edition; we'll add it later.
---
## Output / PDF
### Q6. Where can I re-download a generated PDF?
From the **output history**. Retention depends on your plan, so for long-term storage we recommend keeping a local copy too.
---
### Q7. Can I batch-output multiple PDFs from a list of data?
Yes. There's an **API integration** that accepts many data records at once and outputs that many PDFs in one shot. It needs a bit of technical setup — see the developer-facing **[API reference](/docs/intro)**.
---
## Account / plan
### Q8. I want multiple people to edit a design
**Invite members** to a workspace. Members can co-edit any design within that workspace. See the workspace settings for the invitation flow.
---
### Q9. I want to change my plan
Open **Plan** in the workspace settings to inspect or change your current plan.
---
## Still stuck?
If neither this guide nor the FAQ resolves your question, contact support. We use that feedback to improve and extend this guide.
---
## What to do next
- If you're a developer, see the **[API reference](/docs/intro)** for automating PDF generation via the API.
- To re-read the guide from the beginning, head back to **[Getting Started](./getting-started)**.
---
# docs/user-guide/_glossary.md
# Product Terminology Mini-Glossary (Internal — not published)
> **This page is not published.** It's an internal note used to keep wording consistent across the guide so readers don't get confused by multiple terms for the same concept.
| Concept | Canonical term | Avoid | Notes |
|---------|----------------|-------|-------|
| The unit a customer uses | **Workspace** | Team / Organization | Match the in-product wording |
| Document template | **Design** | Template | The product calls it "design" |
| Element placed on a design | **Object** | Part / Element | Text, image, table, etc. |
| Drawing area | **Canvas** | Board / Stage | The white area in the center of the editor |
| Static value | **Fixed value** | Direct input | The state before turning into a variable |
| Mail-merge value | **Variable** | Property / Field | A slot that gets filled at output time |
| PDF generation | **Output** | Export / Generate | Standardize on "output a PDF" |
| List of generated outputs | **Output history** | History / Outputs | |
## Review checklist
When you write a new page, verify the in-product wording still matches the canonical term in the table above. If the product side has changed, update this table first, then propagate the change across the guide.
---
# OpenAPI Reference
**Report Flow Content Service API** (version 1.0.0)
Report Flow Content Service provides PDF generation capabilities for your reports and documents.
## Authentication
Two authentication methods are supported:
- **API key (`appkey` header, lowercase)** — for single-workspace automation.
- **OAuth 2.0 / OIDC (`Authorization: Bearer ...`)** — Authorization Code + PKCE for per-user
delegation (Make.com etc.) and Client Credentials for backend-to-backend integrations. See
the [OAuth 2.0 guide](https://doc.re-port-flow.com/docs/authentication/oauth) for details.
## Base URL
`https://api.re-port-flow.com/v1`
## Limits
- Maximum request body size: 50MB (after Base64 encoding, roughly 37MB of binary data).
- Sync timeout: 120 seconds. Use the async endpoints for longer jobs.
- Rate limit: per-workspace, 30 req/min for sync endpoints and 100 req/min for async / download endpoints. Exceeding the limit returns 429 with a `Retry-After` header (RFC 9110 §10.2.3).
**Servers:**
- https://api.re-port-flow.com/v1 — Production server
## POST /file/sync/single
**Summary:** 単一PDFの同期生成
指定されたデザインとパラメータから単一のPDFファイルを同期的に生成します。
レスポンスボディとしてPDFファイルが直接返されます。
**制限事項:**
- リクエストサイズ上限: 50MB(Base64エンコード後、実質約37MB相当)
- タイムアウト: 120秒(これより長い処理は非同期エンドポイントを使用)
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
**Tags:** PDF Generation
**Operation ID:** `syncSingle`
**Request Body:**
- Content-Type: `application/json`
- `designId` (string) (required) — デザインID
- `version` (integer) (required) — バージョン番号
- `content` (object) (required)
- `fileName` (string) (required) — ファイル名。`/ \\ : * ? " < > |` および制御文字以外は使用可能。
- `shareType` (string) — 共有タイプ(リクエスト側は数値コード)。 - `"01"`: ワークスペース内共有(デフォルト、レスポンスでは `workspace`) - `"02"`: 招待者共有(レスポンスでは `invited`) - `"03"`: 公開URL共有(レスポンスでは `public`)
- enum: "01", "02", "03"
- `passcodeEnabled` (boolean) — パスコード保護を有効化する。`true` の場合のみレスポンス `share.passcode` にサーバ生成パスコードが一度だけ返却される。
- `passthrough` (object) — 任意の文字列 KV。レスポンスの `X-File-Mapping[].passthrough` または `files[].passthrough` にエコーバックされる。Webhook 追跡やバッチ処理の紐付けに活用できる。
- additionalProperties:
- (string)
- `params` (object) (required) — テンプレートに埋め込むパラメータ
- additionalProperties: any
**Responses:**
- `200` — PDF生成成功
- Content-Type: `application/pdf`
- (string)
- `400` — リクエストが不正
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
- `401` — 認証エラー (`appkey` ヘッダーの値が不正/失効)
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
- `412` — 認証ヘッダー欠落 (`appkey` ヘッダーが含まれていない)
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
- `429` — レート制限超過 (同期エンドポイントは 30 req/min)
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
- `500` — 内部サーバーエラー
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
---
## POST /file/async/single
**Summary:** 単一PDFの非同期生成
指定されたデザインとパラメータから単一のPDFファイルを非同期的に生成します。
即座にファイルURLとIDを返します。
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
**Tags:** PDF Generation
**Operation ID:** `asyncSingle`
**Request Body:**
- Content-Type: `application/json`
- `designId` (string) (required) — デザインID
- `version` (integer) (required) — バージョン番号
- `content` (object) (required)
- `fileName` (string) (required) — ファイル名。`/ \\ : * ? " < > |` および制御文字以外は使用可能。
- `shareType` (string) — 共有タイプ(リクエスト側は数値コード)。 - `"01"`: ワークスペース内共有(デフォルト、レスポンスでは `workspace`) - `"02"`: 招待者共有(レスポンスでは `invited`) - `"03"`: 公開URL共有(レスポンスでは `public`)
- enum: "01", "02", "03"
- `passcodeEnabled` (boolean) — パスコード保護を有効化する。`true` の場合のみレスポンス `share.passcode` にサーバ生成パスコードが一度だけ返却される。
- `passthrough` (object) — 任意の文字列 KV。レスポンスの `X-File-Mapping[].passthrough` または `files[].passthrough` にエコーバックされる。Webhook 追跡やバッチ処理の紐付けに活用できる。
- additionalProperties:
- (string)
- `params` (object) (required) — テンプレートに埋め込むパラメータ
- additionalProperties: any
**Responses:**
- `202` — PDF生成を受け付けました
- Content-Type: `application/json`
- `requestId` (string) — リクエストID(ダウンロードエンドポイントで使用)
- `url` (string) — ZIP ダウンロードURL
- `files` (array)
- items: object
- `fileName` (string)
- `fileId` (string)
- `passthrough` (object) — リクエスト時に指定した `passthrough` の値(指定時のみ)
- additionalProperties:
- (string)
- `share` (object)
- `shareType` (string) — 共有タイプ(レスポンス側は人間可読な名前)。リクエストの `01/02/03` と次のように対応する。 - `workspace` ← `"01"`: ワークスペース内共有 - `invited` ← `"02"`: 招待者共有 - `public` ← `"03"`: 公開URL共有
- enum: "workspace", "invited", "public"
- `url` (string) — ファイル表示URL(全 shareType 共通: `/file/{requestId}/{fileId}`)
- `passcodeEnabled` (boolean) — パスコード有効フラグ
- `passcode` (string) — サーバー生成パスコード(`passcodeEnabled=true` かつ生成直後の一度のみ返却)
- `400` — リクエストが不正
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
- `401` — 認証エラー
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
---
## POST /file/sync/multiple
**Summary:** 複数PDFの同期生成(ZIP)
指定されたデザインと複数のパラメータから複数のPDFファイルを同期的に生成し、ZIPで返します。
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
**Tags:** PDF Generation
**Operation ID:** `syncMultiple`
**Request Body:**
- Content-Type: `application/json`
- `designId` (string) (required) — デザインID
- `version` (integer) (required) — バージョン番号
- `contents` (array) (required)
- items: object
- `fileName` (string) (required) — ファイル名。`/ \\ : * ? " < > |` および制御文字以外は使用可能。
- `shareType` (string) — 共有タイプ(リクエスト側は数値コード)。 - `"01"`: ワークスペース内共有(デフォルト、レスポンスでは `workspace`) - `"02"`: 招待者共有(レスポンスでは `invited`) - `"03"`: 公開URL共有(レスポンスでは `public`)
- enum: "01", "02", "03"
- `passcodeEnabled` (boolean) — パスコード保護を有効化する。`true` の場合のみレスポンス `share.passcode` にサーバ生成パスコードが一度だけ返却される。
- `passthrough` (object) — 任意の文字列 KV。レスポンスの `X-File-Mapping[].passthrough` または `files[].passthrough` にエコーバックされる。Webhook 追跡やバッチ処理の紐付けに活用できる。
- additionalProperties:
- (string)
- `params` (object) (required) — テンプレートに埋め込むパラメータ
- additionalProperties: any
**Responses:**
- `200` — ZIP生成成功
- Content-Type: `application/zip`
- (string)
- `400` — リクエストが不正
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
---
## POST /file/async/multiple
**Summary:** 複数PDFの非同期生成(ZIP)
指定されたデザインと複数のパラメータから複数のPDFファイルを非同期的に生成し、ZIPで保存します。
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
**Tags:** PDF Generation
**Operation ID:** `asyncMultiple`
**Request Body:**
- Content-Type: `application/json`
- `designId` (string) (required) — デザインID
- `version` (integer) (required) — バージョン番号
- `contents` (array) (required)
- items: object
- `fileName` (string) (required) — ファイル名。`/ \\ : * ? " < > |` および制御文字以外は使用可能。
- `shareType` (string) — 共有タイプ(リクエスト側は数値コード)。 - `"01"`: ワークスペース内共有(デフォルト、レスポンスでは `workspace`) - `"02"`: 招待者共有(レスポンスでは `invited`) - `"03"`: 公開URL共有(レスポンスでは `public`)
- enum: "01", "02", "03"
- `passcodeEnabled` (boolean) — パスコード保護を有効化する。`true` の場合のみレスポンス `share.passcode` にサーバ生成パスコードが一度だけ返却される。
- `passthrough` (object) — 任意の文字列 KV。レスポンスの `X-File-Mapping[].passthrough` または `files[].passthrough` にエコーバックされる。Webhook 追跡やバッチ処理の紐付けに活用できる。
- additionalProperties:
- (string)
- `params` (object) (required) — テンプレートに埋め込むパラメータ
- additionalProperties: any
**Responses:**
- `202` — ZIP生成を受け付けました
- Content-Type: `application/json`
- `requestId` (string) — リクエストID(ダウンロードエンドポイントで使用)
- `url` (string) — ZIP ダウンロードURL
- `files` (array)
- items: object
- `fileName` (string)
- `fileId` (string)
- `passthrough` (object) — リクエスト時に指定した `passthrough` の値(指定時のみ)
- additionalProperties:
- (string)
- `share` (object)
- `shareType` (string) — 共有タイプ(レスポンス側は人間可読な名前)。リクエストの `01/02/03` と次のように対応する。 - `workspace` ← `"01"`: ワークスペース内共有 - `invited` ← `"02"`: 招待者共有 - `public` ← `"03"`: 公開URL共有
- enum: "workspace", "invited", "public"
- `url` (string) — ファイル表示URL(全 shareType 共通: `/file/{requestId}/{fileId}`)
- `passcodeEnabled` (boolean) — パスコード有効フラグ
- `passcode` (string) — サーバー生成パスコード(`passcodeEnabled=true` かつ生成直後の一度のみ返却)
---
## GET /file/download/{uuid}
**Summary:** ZIP一括ダウンロード
指定されたUUIDのZIPファイルをダウンロードします。
**Tags:** File Download
**Operation ID:** `downloadZip`
**Parameters:**
- `uuid` in path (string) (required) — ファイルUUID
**Responses:**
- `200` — ZIPダウンロード成功
- Content-Type: `application/zip`
- (string)
- `404` — ファイルが見つかりません
- Content-Type: `application/json`
- `statusCode` (integer) — HTTPステータスコード
- `message` (string) — エラーメッセージ
- `error` (string) — エラータイプ
---
## GET /file/download/{uuid}/{fileId}
**Summary:** 個別ファイルダウンロード
指定されたUUIDとファイルIDの個別ファイルをダウンロードします。
**Tags:** File Download
**Operation ID:** `downloadSingleFile`
**Parameters:**
- `uuid` in path (string) (required) — ファイルUUID
- `fileId` in path (string) (required) — ファイルID
**Responses:**
- `200` — ファイルダウンロード成功
- Content-Type: `application/pdf`
- (string)
- `404` — ファイルが見つかりません
---
## GET /file/design/parameter/{designId}
**Summary:** デザインパラメータ構造を取得
指定されたデザインIDのパラメータ構造を取得します。
PDFやサムネイル生成時に必要なパラメータのスキーマを確認できます。
**Tags:** Design Parameters
**Operation ID:** `getDesignParameters`
**Parameters:**
- `designId` in path (string) (required) — デザインID
- `version` in query (integer) — デザインのバージョン番号(省略時は最新バージョン)
**Responses:**
- `200` — パラメータ構造の取得成功
- Content-Type: `application/json`
- additionalProperties:
- oneOf:
- (string)
- (object)
- array of object
- (object)
- `404` — デザインが見つかりません
- `500` — 内部サーバーエラー
---
## Full OpenAPI Specification (YAML)
```yaml
openapi: 3.0.0
info:
title: Report Flow Content Service API
description: |
Report Flow Content Service provides PDF generation capabilities for your reports and documents.
## Authentication
Two authentication methods are supported:
- **API key (`appkey` header, lowercase)** — for single-workspace automation.
- **OAuth 2.0 / OIDC (`Authorization: Bearer ...`)** — Authorization Code + PKCE for per-user
delegation (Make.com etc.) and Client Credentials for backend-to-backend integrations. See
the [OAuth 2.0 guide](https://doc.re-port-flow.com/docs/authentication/oauth) for details.
## Base URL
`https://api.re-port-flow.com/v1`
## Limits
- Maximum request body size: 50MB (after Base64 encoding, roughly 37MB of binary data).
- Sync timeout: 120 seconds. Use the async endpoints for longer jobs.
- Rate limit: per-workspace, 30 req/min for sync endpoints and 100 req/min for async / download endpoints. Exceeding the limit returns 429 with a `Retry-After` header (RFC 9110 §10.2.3).
version: 1.0.0
contact:
name: Monepla Support
url: https://re-port-flow.com
servers:
- url: https://api.re-port-flow.com/v1
description: Production server
security:
- ApiKeyAuth: []
- OAuth2:
- pdf:generate
- designs:read
- templates:read
tags:
- name: PDF Generation
description: PDF file generation operations (sync and async)
- name: File Download
description: Download generated PDF files
- name: Design Parameters
description: Get design parameter structure
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: appkey
description: |
Authentication requires a single header:
- `appkey`: Your application key (lowercase header name)
OAuth2:
type: oauth2
description: |
OAuth 2.0 / OpenID Connect. The OAuth flow (Authorize / Token / UserInfo) is hosted on
`https://re-port-flow.com/api/v1` (a different host from this protected resource API).
See the [OAuth 2.0 guide](https://doc.re-port-flow.com/docs/authentication/oauth).
flows:
authorizationCode:
authorizationUrl: https://re-port-flow.com/api/v1/oauth/authorize
tokenUrl: https://re-port-flow.com/api/v1/oauth/token
refreshUrl: https://re-port-flow.com/api/v1/oauth/token
scopes:
openid: Confirm the user's id (OIDC)
profile: Profile info (this implementation includes the email address)
designs:read: Read designs (list and detail)
designs:write: Create and edit designs
templates:read: Read templates (list and detail)
templates:write: Create and edit templates
pdf:generate: Generate PDFs from a template
clientCredentials:
tokenUrl: https://re-port-flow.com/api/v1/oauth/token
scopes:
designs:read: Read designs (list and detail)
designs:write: Create and edit designs
templates:read: Read templates (list and detail)
templates:write: Create and edit templates
pdf:generate: Generate PDFs from a template
schemas:
ContentDto:
type: object
required:
- fileName
- params
properties:
fileName:
type: string
description: |
ファイル名。`/ \\ : * ? " < > |` および制御文字以外は使用可能。
example: invoice_2024.pdf
shareType:
type: string
enum: ['01', '02', '03']
default: '01'
description: |
共有タイプ(リクエスト側は数値コード)。
- `"01"`: ワークスペース内共有(デフォルト、レスポンスでは `workspace`)
- `"02"`: 招待者共有(レスポンスでは `invited`)
- `"03"`: 公開URL共有(レスポンスでは `public`)
example: '01'
passcodeEnabled:
type: boolean
default: false
description: |
パスコード保護を有効化する。`true` の場合のみレスポンス `share.passcode` にサーバ生成パスコードが一度だけ返却される。
passthrough:
type: object
additionalProperties:
type: string
description: |
任意の文字列 KV。レスポンスの `X-File-Mapping[].passthrough` または `files[].passthrough` にエコーバックされる。Webhook 追跡やバッチ処理の紐付けに活用できる。
example:
orderId: ORD-2024-001
userId: user-abc123
params:
type: object
additionalProperties: true
description: テンプレートに埋め込むパラメータ
example:
customerName: "山田太郎"
invoiceNumber: "INV-2024-001"
items:
- name: "商品A"
price: 1000
quantity: 2
SingleReq:
type: object
required:
- designId
- version
- content
properties:
designId:
type: string
format: uuid
description: デザインID
example: "550e8400-e29b-41d4-a716-446655440000"
version:
type: integer
description: バージョン番号
example: 1
content:
$ref: '#/components/schemas/ContentDto'
MultipleReq:
type: object
required:
- designId
- version
- contents
properties:
designId:
type: string
format: uuid
description: デザインID
example: "550e8400-e29b-41d4-a716-446655440000"
version:
type: integer
description: バージョン番号
example: 1
contents:
type: array
minItems: 1
items:
$ref: '#/components/schemas/ContentDto'
ExportRes:
type: object
properties:
requestId:
type: string
format: uuid
description: リクエストID(ダウンロードエンドポイントで使用)
example: "550e8400-e29b-41d4-a716-446655440000"
url:
type: string
format: uri
description: ZIP ダウンロードURL
example: "https://api.re-port-flow.com/v1/file/download/550e8400-e29b-41d4-a716-446655440000"
files:
type: array
items:
type: object
properties:
fileName:
type: string
fileId:
type: string
passthrough:
type: object
additionalProperties: { type: string }
description: |
リクエスト時に指定した `passthrough` の値(指定時のみ)
share:
$ref: '#/components/schemas/ShareResponseDto'
ShareResponseDto:
type: object
properties:
shareType:
type: string
enum: [workspace, invited, public]
description: |
共有タイプ(レスポンス側は人間可読な名前)。リクエストの `01/02/03` と次のように対応する。
- `workspace` ← `"01"`: ワークスペース内共有
- `invited` ← `"02"`: 招待者共有
- `public` ← `"03"`: 公開URL共有
url:
type: string
format: uri
description: |
ファイル表示URL(全 shareType 共通: `/file/{requestId}/{fileId}`)
example: "https://app.re-port-flow.com/file/550e8400-e29b-41d4-a716-446655440000/file_123456"
passcodeEnabled:
type: boolean
description: パスコード有効フラグ
passcode:
type: string
description: |
サーバー生成パスコード(`passcodeEnabled=true` かつ生成直後の一度のみ返却)
GetDesignParametersResDto:
type: object
additionalProperties:
oneOf:
- type: string
- type: object
- type: array
items:
type: object
example:
params1: "string"
params2: "number"
params3: "date"
params4:
params4-1: "string"
params5:
- params51: "string"
params52: "number"
Error:
type: object
properties:
statusCode:
type: integer
description: HTTPステータスコード
message:
type: string
description: エラーメッセージ
error:
type: string
description: エラータイプ
paths:
/file/sync/single:
post:
tags:
- PDF Generation
summary: 単一PDFの同期生成
description: |
指定されたデザインとパラメータから単一のPDFファイルを同期的に生成します。
レスポンスボディとしてPDFファイルが直接返されます。
**制限事項:**
- リクエストサイズ上限: 50MB(Base64エンコード後、実質約37MB相当)
- タイムアウト: 120秒(これより長い処理は非同期エンドポイントを使用)
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
operationId: syncSingle
x-codeSamples:
- lang: cURL
source: |
curl -X POST https://api.re-port-flow.com/v1/file/sync/single \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"params": {
"customerName": "山田太郎",
"invoiceNumber": "INV-2024-001",
"amount": 10000
}
}
}' \
--output invoice.pdf
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SingleReq'
example:
designId: "550e8400-e29b-41d4-a716-446655440000"
version: 1
content:
fileName: "invoice.pdf"
params:
customerName: "山田太郎"
invoiceNumber: "INV-2024-001"
amount: 10000
responses:
'200':
description: PDF生成成功
headers:
Content-Type:
schema:
type: string
example: application/pdf
Content-Length:
schema:
type: integer
example: 102400
description: PDF ファイルサイズ (bytes)
Content-Disposition:
schema:
type: string
example: 'attachment; filename="invoice_2024.pdf"'
File-URL:
schema:
type: string
format: uri
example: "https://api.re-port-flow.com/v1/file/download/{requestId}"
description: |
ワークスペースのダウンロードエンドポイント URL (`ExportRes.url` と同形式)。
Request-Id:
schema:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
description: リクエスト ID。後続のファイルダウンロード API 等で使用。
X-File-Mapping:
schema:
type: string
description: |
生成ファイルのメタデータと共有設定 (JSON 配列文字列)。
**URL エンコード済み**で返るため、クライアントは `decodeURIComponent()` してから `JSON.parse()` すること。
デコード後の構造は `[{ fileId, fileName, share, passthrough? }, ...]` (`share` は `ShareResponseDto`)。
content:
application/pdf:
schema:
type: string
format: binary
'400':
description: リクエストが不正
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: 認証エラー (`appkey` ヘッダーの値が不正/失効)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'412':
description: 認証ヘッダー欠落 (`appkey` ヘッダーが含まれていない)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: レート制限超過 (同期エンドポイントは 30 req/min)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: 内部サーバーエラー
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/file/async/single:
post:
tags:
- PDF Generation
summary: 単一PDFの非同期生成
description: |
指定されたデザインとパラメータから単一のPDFファイルを非同期的に生成します。
即座にファイルURLとIDを返します。
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
operationId: asyncSingle
x-codeSamples:
- lang: cURL
source: |
curl -X POST https://api.re-port-flow.com/v1/file/async/single \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"params": {
"customerName": "山田太郎",
"invoiceNumber": "INV-2024-001",
"amount": 10000
}
}
}'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SingleReq'
responses:
'202':
description: PDF生成を受け付けました
content:
application/json:
schema:
$ref: '#/components/schemas/ExportRes'
'400':
description: リクエストが不正
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: 認証エラー
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/file/sync/multiple:
post:
tags:
- PDF Generation
summary: 複数PDFの同期生成(ZIP)
description: |
指定されたデザインと複数のパラメータから複数のPDFファイルを同期的に生成し、ZIPで返します。
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
operationId: syncMultiple
x-codeSamples:
- lang: cURL
source: |
curl -X POST https://api.re-port-flow.com/v1/file/sync/multiple \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"contents": [
{
"fileName": "invoice_001.pdf",
"params": {
"customerName": "山田太郎",
"invoiceNumber": "INV-001"
}
},
{
"fileName": "invoice_002.pdf",
"params": {
"customerName": "佐藤花子",
"invoiceNumber": "INV-002"
}
}
]
}' \
--output invoices.zip
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MultipleReq'
example:
designId: "550e8400-e29b-41d4-a716-446655440000"
version: 1
contents:
- fileName: "invoice_001.pdf"
params:
customerName: "山田太郎"
invoiceNumber: "INV-001"
- fileName: "invoice_002.pdf"
params:
customerName: "佐藤花子"
invoiceNumber: "INV-002"
responses:
'200':
description: ZIP生成成功
headers:
Content-Type:
schema:
type: string
example: application/zip
Content-Length:
schema:
type: integer
example: 307200
description: ZIP ファイルサイズ (bytes)
Content-Disposition:
schema:
type: string
example: 'attachment; filename="files.zip"'
File-URL:
schema:
type: string
format: uri
example: "https://api.re-port-flow.com/v1/file/download/{requestId}"
description: |
ワークスペースのダウンロードエンドポイント URL (`ExportRes.url` と同形式)。
Request-Id:
schema:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
description: リクエスト ID。後続のファイルダウンロード API 等で使用。
X-File-Mapping:
schema:
type: string
description: |
各 PDF のメタデータと共有設定 (JSON 配列文字列)。
**URL エンコード済み**で返るため、クライアントは `decodeURIComponent()` してから `JSON.parse()` すること。
デコード後の構造は `[{ fileId, fileName, share, passthrough? }, ...]` (`share` は `ShareResponseDto`)。
content:
application/zip:
schema:
type: string
format: binary
'400':
description: リクエストが不正
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/file/async/multiple:
post:
tags:
- PDF Generation
summary: 複数PDFの非同期生成(ZIP)
description: |
指定されたデザインと複数のパラメータから複数のPDFファイルを非同期的に生成し、ZIPで保存します。
**Webhook通知:** PDF生成完了時、ワークスペースに設定されたWebhook URLに通知が送信されます。詳細は[Webhook通知ガイド](https://doc.re-port-flow.com/docs/guides/webhooks)を参照してください。
operationId: asyncMultiple
x-codeSamples:
- lang: cURL
source: |
curl -X POST https://api.re-port-flow.com/v1/file/async/multiple \
-H "appkey: your-application-key" \
-H "Content-Type: application/json" \
-d '{
"designId": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"contents": [
{
"fileName": "invoice_001.pdf",
"params": {
"customerName": "山田太郎",
"invoiceNumber": "INV-001"
}
},
{
"fileName": "invoice_002.pdf",
"params": {
"customerName": "佐藤花子",
"invoiceNumber": "INV-002"
}
}
]
}'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MultipleReq'
responses:
'202':
description: ZIP生成を受け付けました
content:
application/json:
schema:
$ref: '#/components/schemas/ExportRes'
/file/download/{uuid}:
get:
tags:
- File Download
summary: ZIP一括ダウンロード
description: |
指定されたUUIDのZIPファイルをダウンロードします。
operationId: downloadZip
x-codeSamples:
- lang: cURL
source: |
curl -X GET https://api.re-port-flow.com/v1/file/download/550e8400-e29b-41d4-a716-446655440000 \
-H "appkey: your-application-key" \
--output files.zip
parameters:
- name: uuid
in: path
required: true
schema:
type: string
format: uuid
description: ファイルUUID
responses:
'200':
description: ZIPダウンロード成功
content:
application/zip:
schema:
type: string
format: binary
'404':
description: ファイルが見つかりません
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/file/download/{uuid}/{fileId}:
get:
tags:
- File Download
summary: 個別ファイルダウンロード
description: |
指定されたUUIDとファイルIDの個別ファイルをダウンロードします。
operationId: downloadSingleFile
x-codeSamples:
- lang: cURL
source: |
curl -X GET https://api.re-port-flow.com/v1/file/download/550e8400-e29b-41d4-a716-446655440000/file_123456 \
-H "appkey: your-application-key" \
--output invoice.pdf
parameters:
- name: uuid
in: path
required: true
schema:
type: string
format: uuid
description: ファイルUUID
- name: fileId
in: path
required: true
schema:
type: string
description: ファイルID
responses:
'200':
description: ファイルダウンロード成功
headers:
File-ID:
schema:
type: string
content:
application/pdf:
schema:
type: string
format: binary
'404':
description: ファイルが見つかりません
/file/design/parameter/{designId}:
get:
tags:
- Design Parameters
summary: デザインパラメータ構造を取得
description: |
指定されたデザインIDのパラメータ構造を取得します。
PDFやサムネイル生成時に必要なパラメータのスキーマを確認できます。
operationId: getDesignParameters
x-codeSamples:
- lang: cURL
source: |
curl -X GET https://api.re-port-flow.com/v1/file/design/parameter/550e8400-e29b-41d4-a716-446655440000 \
-H "appkey: your-application-key"
- lang: cURL (with version)
source: |
curl -X GET "https://api.re-port-flow.com/v1/file/design/parameter/550e8400-e29b-41d4-a716-446655440000?version=1" \
-H "appkey: your-application-key"
parameters:
- name: designId
in: path
required: true
schema:
type: string
format: uuid
description: デザインID
- name: version
in: query
required: false
schema:
type: integer
description: デザインのバージョン番号(省略時は最新バージョン)
responses:
'200':
description: パラメータ構造の取得成功
content:
application/json:
schema:
$ref: '#/components/schemas/GetDesignParametersResDto'
'404':
description: デザインが見つかりません
'500':
description: 内部サーバーエラー
```