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

単一PDF同期生成

POST /file/sync/single エンドポイントは、指定されたテンプレートとパラメータから単一のPDFファイルを同期的に生成し、レスポンスボディにPDFバイナリをそのまま返します。タイムアウトは60秒、リクエストサイズの上限は50MB(Base64エンコード後)です。画面操作に対してその場でPDFを返したい場合に使います。

エンドポイント情報​

  • URL: https://api.re-port-flow.com/v1/file/sync/single
  • メソッド: POST
  • 認証: appkey ヘッダーが必要
  • タイムアウト: 60秒
  • リクエストサイズ上限: 50MB(Base64エンコード後、実質約37MB相当)

使用例​

cURL​

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": "0eUDdgAjNXrrItA2",
"version": 1,
"content": {
"fileName": "invoice.pdf",
"shareType": "01",
"passcodeEnabled": false,
"params": {
"customerName": "山田太郎",
"invoiceNumber": "INV-2024-001",
"amount": 10000
}
}
}' \
--output invoice.pdf

JavaScript (Node.js)​

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);

// レスポンスヘッダーから情報取得
const requestId = response.headers['request-id'];
const fileUrl = response.headers['file-url'];
// X-File-Mapping は URL エンコード済みの JSON 配列。
// decodeURIComponent → JSON.parse の順で復号する。
const fileMapping = JSON.parse(decodeURIComponent(response.headers['x-file-mapping']));

console.log('PDF生成成功:', { requestId, fileUrl, fileMapping });
return { data: response.data, requestId, fileUrl, fileMapping };
} catch (error) {
console.error('PDF生成エラー:', error.response?.data || error.message);
throw error;
}
}

// 使用例
generatePDF({
designId: '0eUDdgAjNXrrItA2',
version: 1,
fileName: 'invoice.pdf',
shareType: '01',
passcodeEnabled: false,
data: {
customerName: '山田太郎',
invoiceNumber: 'INV-2024-001',
amount: 10000
}
});

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)

# レスポンスヘッダーから情報取得
request_id = response.headers.get('request-id')
file_url = response.headers.get('file-url')
# X-File-Mapping は URL エンコード済みの JSON 配列。unquote → json.loads の順で復号する。
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': '0eUDdgAjNXrrItA2',
'version': 1,
'file_name': 'invoice.pdf',
'share_type': '01',
'passcode_enabled': False,
'data': {
'customerName': '山田太郎',
'invoiceNumber': 'INV-2024-001',
'amount': 10000
}
})

リクエストパラメータ​

フィールド型必須説明
designIdstring✓テンプレート ID(16文字の英数字。例: 0eUDdgAjNXrrItA2)
versioninteger✓バージョン番号
webhookUrlstring-このリクエストの Webhook 通知先(https、2048文字以内)。ワークスペースに登録済みで有効な Webhook エンドポイントの URL と一致する必要があり、一致しなければ 400 WEBHOOK_URL_NOT_ALLOWED。指定すると、その宛先だけがこのリクエストのイベントを受け取る(詳細)
content.fileNamestring✓ファイル名(/ \\ : * ? " < > | および制御文字以外は使用可能)
content.shareTypestring-共有タイプ(リクエストは数値コード)。"01"=ワークスペース内共有(デフォルト) / "02"=招待者共有 / "03"=公開URL共有。レスポンスの share.shareType は workspace / invited / public の人間可読な名前で返ります
content.passcodeEnabledboolean-パスコード保護(デフォルト: false)
content.passthroughobject-レスポンスの X-File-Mapping に透過する任意のメタデータ(例: { "pageId": "abc123" })。トップレベルの文字列・数値は帳票検索メタデータとしても保存されます
content.paramsobject✓テンプレートに埋め込むパラメータ(テンプレートのパラメータ取得APIで構造を確認可能)

レスポンス​

成功時 (200 OK)​

レスポンスボディ: PDFファイル(バイナリ)

レスポンスヘッダー:

ヘッダー説明例
Content-Typeコンテンツタイプapplication/pdf
Content-Lengthファイルサイズ(バイト)102400
Content-Dispositionファイル名attachment; filename="invoice.pdf"
File-URL生成結果を確認できるアプリ画面(出力履歴)のURL。ファイルの直接ダウンロードURLではないhttps://re-port-flow.com/{workspaceId}/templates/{designId}/outputs?requestId={requestId}
Request-IdリクエストIDkZ3mP9qL2xV7nR4t
X-File-Mappingファイル情報と共有設定(JSON 配列)。URL エンコード済みで返るため、クライアントは decodeURIComponent してから JSON.parse すること下記参照
File-URL はダウンロードURLではありません

File-URL は生成結果を確認できるアプリ画面(出力履歴)へのリンクで、開くにはRe:port Flowへのログインが必要です。プログラムからファイルを取得する場合はPDF のダウンロードのエンドポイントを使ってください(requestId でZIP一括、requestId + fileId で個別PDF)。

以前このヘッダーは「ZIPダウンロードURL」と記載していましたが、実装が返す値と食い違っていたため、実装に合わせて訂正しました。レスポンス自体の挙動は変わっていません。

X-File-Mapping の構造:

[
{
"fileId": "Hn4sT8wQ1cV6bX2e",
"fileName": "invoice.pdf",
"passthrough": { "pageId": "abc123" },
"share": {
"shareType": "workspace",
"url": "https://re-port-flow.com/file/{requestId}/{fileId}",
"passcodeEnabled": false
}
}
]

passthrough フィールドはリクエスト時に content.passthrough を指定した場合のみ含まれます。

passthrough の使い所

ReportFlow はセキュリティとペイロードサイズの観点から、レスポンスや Webhook 通知に params(生成データ)を返しません。

受信側で「どの業務レコードに対する PDF か」を識別したい場合は、 リクエスト時に passthrough に自社DBのIDなどを入れてください。 レスポンスや Webhook でそのまま返却されます。

{
"fileName": "invoice.pdf",
"passthrough": { "invoiceId": "INV-001", "tenantId": "acme" },
"params": { "customerName": "山田太郎", "amount": 10000 }
}

Webhook 受信時に passthrough の値がそのまま返るため、invoiceId から DB を引いて該当レコードを更新する、といった処理が組めます。 params(顧客名や金額)は外部に送信されません。

さらに passthrough のトップレベルにある文字列・数値は、帳票検索用のメタデータ としても保存されます(生成PDF内の XMP メタデータと、Re:port Flow アプリの帳票検索 インデックス)。対象になる値の条件・上限と、個人情報を入れてはいけない理由は passthrough と帳票検索メタデータを参照してください。

識別子を JSON で受け取る(?response=json)​

URL に ?response=json を付けると、PDFの本体の代わりに識別子を JSON で返します。レスポンスヘッダー(Request-Id / File-URL / X-File-Mapping)を読めないツール(Coze など)向けです。

curl -X POST "https://api.re-port-flow.com/v1/file/sync/single?response=json" \
-H "appkey: YOUR_APP_KEY" \
-H "Content-Type: application/json" \
-d @request.json
{
"requestId": "Xk3pQ9vB2mN7aR1t",
"fileUrl": "https://re-port-flow.com/{workspaceId}/templates/{designId}/outputs?requestId=Xk3pQ9vB2mN7aR1t",
"files": [
{
"fileId": "a8Fh2LkQ9zT4wE6y",
"fileName": "invoice-001",
"pageCount": 1,
"downloadPath": "/v1/file/download/Xk3pQ9vB2mN7aR1t/a8Fh2LkQ9zT4wE6y",
"share": { "shareType": "workspace" }
}
]
}
  • 切り替わるのはクエリ ?response=json を付けたときだけです。Accept: application/json ヘッダーでは切り替わりません(既存の連携が意図せず JSON を受け取らないようにするため)。
  • fileUrl は出力履歴画面の URL で、ファイル本体の URL ではありません。PDF は、ホスト(https://api.re-port-flow.com)に downloadPath を連結した URL に appkey(または Bearer)を付けて GET すると取得できます(PDF ダウンロード)。
  • 生成とページ数の消費は、PDF 本体を返す場合と同じです。

エラー時​

エラー本文の形式(statusCode / code / message / error / details / docsUrl)とコードの一覧は エラーコード一覧 を参照してください。message は既定で英語です。

412 Precondition Failed​

{
"statusCode": 412,
"code": "AUTH_HEADER_MISSING",
"message": "Authentication header is missing. Send an `appkey` header or `Authorization: Bearer <token>`.",
"error": "Precondition Failed",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#auth_header_missing"
}

原因: appkey ヘッダーも Authorization: Bearer <token> ヘッダーも無い、または appkey ヘッダーの形式が不正

400 Bad Request​

{
"statusCode": 400,
"code": "FILE_NAME_INVALID",
"message": [
"content.fileName contains characters that are not allowed (/ \\ : * ? \" < > | and control characters)."
],
"error": "Bad Request",
"details": [
{
"field": "content.fileName",
"code": "FILE_NAME_INVALID",
"message": "fileName contains characters that are not allowed (/ \\ : * ? \" < > | and control characters)."
}
],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#file_name_invalid"
}

原因: リクエストボディが不正(この例はファイル名の禁止文字)。種類の異なる検証エラーが混ざると code は VALIDATION_FAILED になります。params の型の不一致は PARAMS_TYPE_MISMATCH です。designId は 0eUDdgAjNXrrItA2 のような16文字の英数字で、形式チェックは行わないため、誤った designId は 400 ではなく 404 DESIGN_VERSION_NOT_FOUND になります。

401 Unauthorized​

{
"statusCode": 401,
"code": "INVALID_CREDENTIALS",
"message": "Invalid API key or access token.",
"error": "Unauthorized",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#invalid_credentials"
}

原因: 認証エラー

対処法:

  • appkey ヘッダーの値(またはアクセストークン)が正しいことを確認

500 Internal Server Error​

{
"statusCode": 500,
"code": "INTERNAL_ERROR",
"message": "Internal Server Error",
"error": "Internal Server Error",
"details": [],
"docsUrl": "https://doc.re-port-flow.com/en/docs/guides/error-codes#internal_error"
}

ベストプラクティス​

1. タイムアウト処理​

同期APIは60秒でタイムアウトします。大きなPDFや複雑なテンプレートの場合は、非同期APIの使用を検討してください。クライアント側のタイムアウトは、サーバー側の 504 を受け取れるよう 60 秒より少し長くします。

const response = await axios.post(url, data, {
timeout: 70000 // 70秒(サーバー側は 60 秒で 504 を返す)
});

2. リトライロジック​

一時的なサーバーエラー(500 / 502 / 503)の場合は、指数バックオフでリトライを実装します(例では 500 だけを再送しています)。504 / 524 は生成が続いていて二重に計上されうるので再送しません(エラーハンドリングを参照)。

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. ファイル名のサニタイゼーション​

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

ユースケース​

ケース1: 請求書の即時生成​

ユーザーが「請求書ダウンロード」ボタンをクリックしたときに、即座にPDFを生成してダウンロードさせる。

app.get('/api/invoices/:id/download', requireLogin, async (req, res) => {
// ログイン中の利用者がこの請求書を見てよいかを確かめる(他人の ID を指定されても返さない)
if (!(await canViewInvoice(req.user, req.params.id))) return res.status(404).end();
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);
});

ケース2: プレビュー生成​

テンプレートの編集画面でユーザーがプレビューボタンをクリックしたときに、リアルタイムでPDFを生成して表示する。

この例では、ブラウザは自社サーバーを経由して PDF を受け取ります。

// 自社サーバー側(appkey はここにだけ置く)
// 認証なしで designId / params を中継すると、appkey と同じ権限を誰にでも渡すことになる。
// 自社の認証(requireLogin)と、受け付ける designId の許可リストを必ず入れる。
const PREVIEW_DESIGN_IDS = new Set(['0eUDdgAjNXrrItA2']);

app.post('/api/preview', requireLogin, async (req, res) => {
if (!PREVIEW_DESIGN_IDS.has(req.body.designId)) return res.status(400).end();
const pdf = await generatePDF({
designId: req.body.designId,
version: 1,
fileName: 'preview.pdf',
data: req.body.params
});

res.setHeader('Content-Type', 'application/pdf');
res.send(pdf.data);
});

// ブラウザ側: 自社サーバーから受け取った PDF を表示
async function showPreview(designId, params) {
const res = await fetch('/api/preview', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ designId, params })
});
const url = URL.createObjectURL(await res.blob());
window.open(url, '_blank');
}

よくある質問​

同期生成と非同期生成はどちらを使うべきですか​

画面操作に対してその場で結果を返したい場合は同期エンドポイントを使います。生成に時間がかかりうる場合(60 秒を超えうる場合。制限事項)や、タイムアウトを避けたい場合は単一PDF非同期生成を使ってください。

タイムアウトした場合はどうなりますか​

同期エンドポイントは60秒でタイムアウトし、API の手前のロードバランサーが 504 Gateway Timeout を返します(本文は JSON とは限りません。制限事項)。大きなPDFや複雑なテンプレートの場合は、非同期エンドポイントへの切り替えかテンプレートの簡素化を検討してください(詳細は制限事項)。

次のステップ​