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

PDF生成ガイド

Re:port Flow の PDF 生成には、その場で PDF バイナリを受け取る同期生成と、requestId だけを即座に受け取って後から取得する非同期生成の2モードがあります。即座に結果が必要なら同期、大量生成やバックグラウンド処理なら非同期を選びます。同期は 120 秒でタイムアウトします。このガイドでは両モードの呼び出し方、パラメータ構造、エラー処理までを通して説明します。

同期生成と非同期生成はどう使い分ける?

即座に結果が必要なら同期、大量生成やバックグラウンド処理なら非同期です。同期は 120 秒のタイムアウトがあるため、それを超えうる処理は非同期に寄せてください。

PDF生成には2つのモードがあります:

モードエンドポイントレスポンス用途
同期生成/file/sync/singlePDFバイナリ即座に結果が必要な場合
非同期生成/file/async/singlerequestId / url / files 配列 (202 Accepted)大量生成やバックグラウンド処理

同期生成

基本的な使い方

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",
"items": [
{
"name": "商品A",
"price": 1000,
"quantity": 2
}
]
}
}
}' \
--output invoice.pdf

レスポンスヘッダー

Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice.pdf"
Content-Length: 123456
Request-Id: 550e8400-e29b-41d4-a716-446655440000
File-URL: https://re-port-flow.com/{workspaceId}/design/{designId}/outputs?requestId={requestId}
X-File-Mapping: %5B%7B%22fileId%22%3A...%7D%5D

X-File-MappingURL エンコード済みの JSON 配列 で、fileId / fileName / share 情報を含みます。クライアント側で decodeURIComponent してから JSON.parse してください。詳細は 単一PDF同期生成エンドポイント を参照。

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,
params: params.data
}
},
{
headers: {
'appkey': process.env.APP_KEY,
'Content-Type': 'application/json'
},
responseType: 'arraybuffer'
}
);

// ファイルに保存
fs.writeFileSync(params.fileName, response.data);

// レスポンスヘッダから情報を取得
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生成エラー:', error.response?.data || error.message);
throw error;
}
}

// 使用例
generatePDF({
designId: '550e8400-e29b-41d4-a716-446655440000',
version: 1,
fileName: 'invoice.pdf',
data: {
customerName: '山田太郎',
invoiceNumber: 'INV-2024-001',
items: [
{ name: '商品A', price: 1000, quantity: 2 }
]
}
});

非同期生成

大量のPDFを生成する場合や、タイムアウトを避けたい場合は非同期生成を使用します。

基本的な使い方

# 1. 生成リクエスト
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": {...}
}
}'

# レスポンス例 (202 Accepted)
{
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://re-port-flow.com/{workspaceId}/design/{designId}/outputs?requestId={requestId}",
"files": [
{
"fileName": "invoice.pdf",
"fileId": "7f3d1a2b-4c5e-6f7a-8b9c-0d1e2f3a4b5c",
"share": {
"shareType": "workspace",
"url": "https://re-port-flow.com/file/{requestId}/{fileId}",
"passcodeEnabled": false
}
}
]
}

詳細は 単一PDF非同期生成エンドポイント を参照。

JavaScript実装例(ポーリング)

// 生成完了までダウンロードAPIをポーリングする。
// 生成中は 404 が返るため、それ以外のエラーは即座に投げる。
async function pollDownload(downloadUrl, { intervalMs = 3000, timeoutMs = 300000 } = {}) {
const deadline = Date.now() + timeoutMs;

for (;;) {
// 残り時間は 1 度だけ計算する。while 条件と timeout 指定で別々に Date.now() を
// 呼ぶと、その間に期限へ到達して timeout: 0 を渡しうる。
// axios は 0 を「タイムアウト無効」と解釈するため、ハングしたリクエストで
// 永久に待ち続けてしまう
const remainingMs = deadline - Date.now();
if (remainingMs <= 0) break;

try {
return await axios.get(downloadUrl, {
headers: { 'appkey': process.env.APP_KEY },
responseType: 'arraybuffer',
timeout: remainingMs,
});
} catch (error) {
// 生成中は 404。タイムアウト(ECONNABORTED)等はそのまま投げて終了する
if (error.response?.status !== 404) throw error;
// 待機も残り時間で頭打ちにする。そのまま intervalMs 待つと、
// 期限直前の 404 や intervalMs > timeoutMs の指定で timeoutMs を超過する
await new Promise(resolve =>
setTimeout(resolve, Math.min(intervalMs, deadline - Date.now())),
);
}
}

throw new Error(`PDF生成がタイムアウトしました: ${downloadUrl}`);
}

async function generatePDFAsync(params) {
// 1. 非同期生成リクエスト
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. ファイルはダウンロードAPIから取得する。
// レスポンスの `url` はアプリ画面(出力履歴)へのリンクで、ログインが必要なため
// プログラムからのダウンロードには使えない。
// ZIP 一括: GET /v1/file/download/{requestId}
// 個別取得: GET /v1/file/download/{requestId}/{fileId}
//
const downloadUrl =
`https://api.re-port-flow.com/v1/file/download/${requestId}/${files[0].fileId}`;

// 3. 生成完了までポーリングする。非同期生成のため、完了前に取得すると 404 になる。
// ポーリングは待ち時間が無駄になるので、実運用では Webhook 通知のほうが確実
// (下記「Webhook通知」を参照)。
const pdfResponse = await pollDownload(downloadUrl);

return {
data: pdfResponse.data,
requestId,
// アプリ画面(出力履歴)へのリンク。ダウンロードURLではない
outputsUrl: url,
files,
};
}

複数PDF生成(ZIP)

複数のPDFを一度に生成してZIPで取得できます。

同期生成

async function generateMultiplePDFs(designId, contents) {
const response = await axios.post(
'https://api.re-port-flow.com/v1/file/sync/multiple',
{
designId,
version: 1,
contents // ContentDto の配列
},
{
headers: {
'appkey': process.env.APP_KEY
},
responseType: 'arraybuffer'
}
);

// X-File-Mapping ヘッダーからファイルマッピングを取得
const fileMapping = JSON.parse(response.headers['x-file-mapping']);
console.log('生成されたファイル:', fileMapping);

return response.data; // ZIP binary
}

// 使用例
const contents = [
{
fileName: 'invoice_001.pdf',
params: { customerName: '山田太郎', invoiceNumber: 'INV-001' }
},
{
fileName: 'invoice_002.pdf',
params: { customerName: '佐藤花子', invoiceNumber: 'INV-002' }
}
];

const zipData = await generateMultiplePDFs('550e8400-...', contents);
fs.writeFileSync('invoices.zip', zipData);

パラメータの構造

デザインパラメータの取得

生成前に、デザインで使用可能なパラメータ構造を確認できます:

curl -X GET https://api.re-port-flow.com/v1/file/design/parameter/{designId}?version=1 \
-H "appkey: your-application-key"

レスポンス例:

{
"customerName": "string",
"invoiceNumber": "string",
"amount": "number",
"items": [
{
"name": "string",
"price": "number",
"quantity": "number"
}
],
"issueDate": "date"
}

パラメータ型の対応

説明
string文字列"山田太郎"
number数値1000
date日付(ISO 8601)"2024-02-12"
objectネストしたオブジェクト{ "name": "値" }
array配列[{ "item": 1 }]

passthrough と帳票検索メタデータ

content.passthrough は、レスポンスや Webhook にそのままエコーバックされる任意のメタデータです。 params(生成データ)はレスポンスにも Webhook にも返らないため、受信側で「どの業務レコードに 対する PDF か」を識別する用途に使います。

{
"fileName": "invoice.pdf",
"passthrough": { "invoiceId": "INV-001", "customerName": "株式会社サンプル" },
"params": { "customerName": "株式会社サンプル", "amount": 10000 }
}

加えて passthrough の値は検索用メタデータとしても保存されます。保存先は次の2か所です。

保存先内容参照方法
生成PDF内の XMP メタデータrf:paramskey=value として埋め込みPDF 受領者の OS 検索(Spotlight / Windows Search)等
Re:port Flow の検索インデックスワークスペースの帳票検索に使用Re:port Flow アプリの帳票検索画面

これにより、生成時に customerNameinvoiceId を渡しておくと、後から アプリの帳票検索画面でその値を使って帳票を探せます。

インデックス対象になる値

passthrough の各値(文字列または数値)は、次の条件でインデックス対象になります。

条件挙動
文字列(string前後の空白を除去して保存。空文字列は対象外
数値(number有限数のみ、10進表記の文字列として保存
値の長さ256 文字を超える分は切り詰めて保存
キーの件数1ファイルあたり最大 64 件。超過分は保存されない

インデックス対象外になった値(空文字列・非有限数・64件超過分)も、レスポンス・Webhook へのエコーバックには従来どおりすべて含まれます(エコーバックの挙動は変わりません)。

個人情報・機微情報は入れないでください

passthrough の値は生成したPDFのメタデータに埋め込まれます。PDF を受け取った相手が メタデータを閲覧でき、相手の OS の検索インデックスにも載ります。

マイナンバー・口座番号・健康情報などの機微情報や、開示したくない個人情報は passthrough に入れないでください。PDF 面に印字したいだけの値は params を使います (この仕組みでは params の値は PDF 内メタデータにも検索インデックスにも保存されません。 テンプレート側でパラメータが個別に検索対象として設定されている場合はこの限りではないため、 心配な場合はテンプレート作成者に確認してください)。

エラーハンドリング

一般的なエラー

400 Bad Request - バリデーションエラー

{
"statusCode": 400,
"message": [
"designId must be a UUID",
"ファイル名に使用できない文字が含まれています(/ \\ : * ? \" < > | および制御文字は使用不可)"
],
"error": "Bad Request"
}

対処法:

  • リクエストボディを確認
  • ファイル名の形式を確認(/ \ : * ? " < > | および制御文字は使用不可)

500 Internal Server Error

{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}

対処法:

  • 一時的なサーバーエラーの可能性
  • リトライロジックを実装
  • 継続する場合はサポートに連絡

リトライ戦略

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) {
// 指数バックオフでリトライ
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
continue;
}
throw error;
}
}
}

ベストプラクティス

1. タイムアウト設定

同期生成は120秒でタイムアウトします。大きなPDFの場合は非同期生成を使用してください。

// axios でタイムアウト設定
const response = await axios.post(url, data, {
timeout: 120000 // 120秒
});

2. ファイル名のサニタイゼーション

function sanitizeFileName(fileName) {
// / \ : * ? " < > | および制御文字を除去
return fileName.replace(/[\/\\:*?"<>|\x00-\x1F]/g, '_');
}

3. パラメータの検証

function validateParams(params, schema) {
// デザインパラメータスキーマと照合
for (const [key, type] of Object.entries(schema)) {
if (!(key in params)) {
throw new Error(`必須パラメータ ${key} が不足しています`);
}
// 型チェック等
}
}

次のステップ