# 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 [![Run in Postman](https://run.pstmn.io/button.svg)](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: 内部サーバーエラー ```