2026/05/19

PDF分割API完全ガイド:ページ範囲指定でPDFを分割する方法

PDF APIPDF操作PDF分割ドキュメント処理REST API

200ページのレポートから特定の章だけを抜き出したい。月次バッチPDFの中から1枚の請求書だけを取り出したい。一括生成した証明書PDFから個別ファイルを分離したい――こうしたニーズに応えるのが、FUNBREW PDFのPDF分割APIです。

分割エンドポイント(POST /api/pdf/split)は、FUNBREWアカウントにすでに保存されているPDFを受け取り、指定したページ(範囲指定・個別指定・組み合わせのいずれでも可)を抽出して、新しい独立したPDFファイルとダウンロードURLを返します。

このガイドでは、APIの仕様・pagesパラメータの書式・cURL/Node.js/Python/PHPのコード例・実際のユースケースパターンを解説します。

PDF生成から始める方はPDF APIクイックスタートを、複数PDFの結合はPDF結合API完全ガイドを、ファイルサイズの圧縮はPDF圧縮API完全ガイドを、PDF操作エンドポイントの全体像はPDF操作ガイドをご覧ください。

動作の仕組み

PDF分割は2ステップで完結します。

  1. FUNBREWアカウント内にPDFが存在している必要があります(POST /api/pdf/generateで生成済み、かつ有効期限内)。
  2. POST /api/pdf/splitにfilenameと抽出したいページを指定して呼び出します。

サーバー側はFPDIライブラリを使って指定ページをインポートし、新しいPDFを生成します。生成されたファイルはサーバーに保存され、ダウンロードURLとして返されます。

このエンドポイントはSaaSエディションのみで利用可能で、プランのpdf.feature:splitフィーチャーゲートが有効である必要があります。

API仕様

POST /api/pdf/split
Content-Type: application/json
Authorization: Bearer {APIキー}

リクエストパラメータ

パラメータ 必須 説明
filename string Yes アカウント内の既存PDFのファイル名
pages string Yes ページ指定文字列(書式は下記参照)
expiration_hours integer No 分割ファイルの有効時間(0〜168、デフォルト24)
max_downloads integer No 有効期限前の最大ダウンロード数(0〜100、デフォルト10)

pagesパラメータの書式

pagesはカンマ区切りのページ番号・範囲指定文字列です。ページ番号は1始まり(1-indexed)です。

書式 意味
単一ページ "3" 3ページ目のみ
範囲指定 "1-5" 1〜5ページ(両端含む)
複数ページ "1,3,5" 1・3・5ページ
組み合わせ "1-3,7,10-12" 1〜3ページ、7ページ、10〜12ページ

出力PDFには指定した順序でページが収録されます。重複したページ番号は自動的に除去されます。いずれかのページ番号が総ページ数を超えた場合、リクエスト全体が422エラーになります(部分的な出力は生成されません)。

成功レスポンス

{
  "success": true,
  "data": {
    "filename": "split-abc123.pdf",
    "download_url": "https://pdf.funbrew.cloud/api/pdf/download/split-abc123.pdf",
    "file_size": 102400,
    "pages_extracted": 3,
    "expires_at": "2026-05-20T10:00:00Z"
  }
}

pages_extractedは出力ファイルのページ数です。

エラーレスポンス

ステータス 理由
422 filenameがアカウントに存在しない、または別のAPIキーで生成したファイル
422 ファイルの有効期限が切れている
422 ストレージ上にファイルが存在しない(すでに削除済み)
422 ページ番号がPDFの総ページ数を超えている

コード例

cURL

PDFを生成してから1〜3ページを分割する例:

# Step 1: PDFを生成(返されたfilenameを取得)
RESPONSE=$(curl -s -X POST https://pdf.funbrew.cloud/api/pdf/generate \
  -H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>第1章</h1><p>...</p><div style=\"page-break-after:always\"></div><h1>第2章</h1><p>...</p><div style=\"page-break-after:always\"></div><h1>第3章</h1><p>...</p>",
    "options": { "format": "A4", "responseFormat": "url" }
  }')
FILENAME=$(echo $RESPONSE | jq -r '.data.filename')

# Step 2: 1〜3ページを抽出
curl -s -X POST https://pdf.funbrew.cloud/api/pdf/split \
  -H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"filename\": \"$FILENAME\",
    \"pages\": \"1-3\",
    \"expiration_hours\": 48,
    \"max_downloads\": 5
  }" | jq .

個別ページ(1、3、5ページ目)を抽出する例:

curl -s -X POST https://pdf.funbrew.cloud/api/pdf/split \
  -H "Authorization: Bearer $FUNBREW_PDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"filename\": \"$FILENAME\",
    \"pages\": \"1,3,5\"
  }" | jq .

Node.js

const API_KEY  = process.env.FUNBREW_PDF_API_KEY;
const BASE_URL = "https://pdf.funbrew.cloud";

/**
 * PDFを生成してサーバー側のfilenameを返す
 */
async function generatePdf(html, options = {}) {
  const response = await fetch(`${BASE_URL}/api/pdf/generate`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      html,
      options: { format: "A4", ...options, responseFormat: "url" },
    }),
  });

  if (!response.ok) {
    throw new Error(`PDF生成に失敗しました: HTTP ${response.status}`);
  }

  const { data } = await response.json();
  return data.filename;
}

/**
 * 保存済みPDFをページ指定で分割する
 *
 * @param {string} filename  - generatePdf()が返したfilename
 * @param {string} pages     - ページ指定 例: "1-3"、"1,3,5"、"2-5,8"
 * @param {object} options   - { expirationHours, maxDownloads }
 */
async function splitPdf(filename, pages, options = {}) {
  const response = await fetch(`${BASE_URL}/api/pdf/split`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      filename,
      pages,
      expiration_hours: options.expirationHours ?? 24,
      max_downloads:    options.maxDownloads ?? 10,
    }),
  });

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`PDF分割に失敗しました: HTTP ${response.status} — ${body}`);
  }

  return response.json();
}

// 使用例: レポートPDFを生成して第2〜3章(2〜3ページ)を抽出
async function extractChapter(reportHtml, chapterPages) {
  console.log("レポートPDFを生成中...");
  const filename = await generatePdf(reportHtml);

  console.log(`ページ ${chapterPages} を抽出中...`);
  const result = await splitPdf(filename, chapterPages, {
    expirationHours: 72,
    maxDownloads: 3,
  });

  console.log("分割完了:");
  console.log("  抽出ページ数:", result.data.pages_extracted);
  console.log("  ダウンロードURL:", result.data.download_url);
  return result.data;
}

extractChapter(
  "<h1>第1章</h1><p>...</p>",  // 実際のレポートHTMLを指定
  "2-3"
).catch(console.error);

Python

import os
import httpx
from typing import Optional

API_KEY  = os.environ["FUNBREW_PDF_API_KEY"]
BASE_URL = "https://pdf.funbrew.cloud"


def generate_pdf(html: str, **options) -> str:
    """PDFを生成してサーバー側のfilenameを返す"""
    response = httpx.post(
        f"{BASE_URL}/api/pdf/generate",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "html": html,
            "options": {"format": "A4", **options, "responseFormat": "url"},
        },
        timeout=120,
    )
    response.raise_for_status()
    return response.json()["data"]["filename"]


def split_pdf(
    filename: str,
    pages: str,
    expiration_hours: int = 24,
    max_downloads: int = 10,
) -> dict:
    """
    保存済みPDFをページ指定で分割する

    Args:
        filename: generate_pdf()が返したfilename
        pages: ページ指定文字列 例: '1-3'、'1,3,5'、'2-5,8,10-12'
        expiration_hours: 0〜168
        max_downloads: 0〜100
    """
    response = httpx.post(
        f"{BASE_URL}/api/pdf/split",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "filename":         filename,
            "pages":            pages,
            "expiration_hours": expiration_hours,
            "max_downloads":    max_downloads,
        },
        timeout=120,
    )
    response.raise_for_status()
    return response.json()


# 使用例: 一括請求書PDFから特定ページを取り出す
def extract_invoice_pages(batch_filename: str, page_numbers: list[int]) -> str:
    """バッチPDFから指定ページを抽出してダウンロードURLを返す"""
    pages_spec = ",".join(str(p) for p in page_numbers)
    print(f"{batch_filename} からページ {pages_spec} を抽出中...")

    result = split_pdf(
        filename=batch_filename,
        pages=pages_spec,
        expiration_hours=48,
        max_downloads=5,
    )

    data = result["data"]
    print(f"抽出ページ数: {data['pages_extracted']}")
    print(f"ファイルサイズ: {data['file_size']} バイト")
    print(f"ダウンロードURL: {data['download_url']}")
    return data["download_url"]


if __name__ == "__main__":
    batch_html = """
    <html><body>
      <div style="page-break-after:always"><h1>請求書 #001</h1><p>株式会社A</p></div>
      <div style="page-break-after:always"><h1>請求書 #002</h1><p>株式会社B</p></div>
      <div><h1>請求書 #003</h1><p>株式会社C</p></div>
    </body></html>
    """
    batch_filename = generate_pdf(batch_html)
    download_url = extract_invoice_pages(batch_filename, [1, 3])
    print("完了:", download_url)

PHP

<?php

class FunbrewPdfSplitter
{
    private string $apiKey;
    private string $baseUrl = 'https://pdf.funbrew.cloud';

    public function __construct(string $apiKey)
    {
        $this->apiKey = $apiKey;
    }

    /**
     * PDFを生成してサーバー側のfilenameを返す
     */
    public function generatePdf(string $html, array $options = []): string
    {
        $payload = [
            'html'    => $html,
            'options' => array_merge(['format' => 'A4', 'responseFormat' => 'url'], $options),
        ];

        $ch = curl_init("{$this->baseUrl}/api/pdf/generate");
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($payload),
            CURLOPT_HTTPHEADER     => [
                "Authorization: Bearer {$this->apiKey}",
                'Content-Type: application/json',
            ],
            CURLOPT_TIMEOUT        => 120,
        ]);

        $body   = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($status !== 200) {
            throw new \RuntimeException("PDF生成に失敗しました: HTTP {$status} — {$body}");
        }

        return json_decode($body, true)['data']['filename'];
    }

    /**
     * 保存済みPDFをページ指定で分割する
     *
     * @param string $filename        generatePdf()が返したfilename
     * @param string $pages           ページ指定 例: '1-3'、'1,3,5'、'2-5,8'
     * @param int    $expirationHours 0〜168(デフォルト24)
     * @param int    $maxDownloads    0〜100(デフォルト10)
     */
    public function split(
        string $filename,
        string $pages,
        int $expirationHours = 24,
        int $maxDownloads = 10
    ): array {
        $payload = [
            'filename'         => $filename,
            'pages'            => $pages,
            'expiration_hours' => $expirationHours,
            'max_downloads'    => $maxDownloads,
        ];

        $ch = curl_init("{$this->baseUrl}/api/pdf/split");
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($payload),
            CURLOPT_HTTPHEADER     => [
                "Authorization: Bearer {$this->apiKey}",
                'Content-Type: application/json',
            ],
            CURLOPT_TIMEOUT        => 120,
        ]);

        $body   = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($status !== 200) {
            throw new \RuntimeException("PDF分割に失敗しました: HTTP {$status} — {$body}");
        }

        return json_decode($body, true);
    }
}

// 使用例(Laravel)
$splitter = new FunbrewPdfSplitter(env('FUNBREW_PDF_API_KEY'));

// 複数ページのPDFを生成
$filename = $splitter->generatePdf(
    '<h1>1ページ目</h1><div style="page-break-after:always"></div>'
    . '<h1>2ページ目</h1><div style="page-break-after:always"></div>'
    . '<h1>3ページ目</h1>'
);

// 1ページ目と3ページ目を抽出
$result = $splitter->split(
    filename:        $filename,
    pages:           '1,3',
    expirationHours: 48,
    maxDownloads:    5
);

echo "ダウンロードURL: " . $result['data']['download_url'] . "\n";
echo "抽出ページ数: " . $result['data']['pages_extracted'] . "\n";

ユースケースパターン

大型レポートの章別配布

レポートを1回だけ生成し、各章を個別PDFとして配布します。再生成は不要です。

chapters = [
    {"name": "エグゼクティブサマリー", "pages": "1-3"},
    {"name": "財務データ",            "pages": "4-12"},
    {"name": "付録",                  "pages": "13-20"},
]

# レポートを1回だけ生成
report_filename = generate_pdf(full_report_html)

# 章ごとに分割
chapter_urls = {}
for chapter in chapters:
    result = split_pdf(
        filename=report_filename,
        pages=chapter["pages"],
        expiration_hours=168,   # 7日間
        max_downloads=50,
    )
    chapter_urls[chapter["name"]] = result["data"]["download_url"]
    print(f"{chapter['name']}: {result['data']['download_url']}")

月次バッチから1枚の請求書を再送

月次バッチPDFの中から特定の請求書ページを取り出して再送します。

async function resendInvoice(batchFilename, invoicePageNumber, recipientEmail) {
  // 指定ページを抽出
  const result = await splitPdf(batchFilename, String(invoicePageNumber), {
    expirationHours: 24,
    maxDownloads: 2,
  });

  const downloadUrl = result.data.download_url;

  // メールで送信
  await sendEmail({
    to:      recipientEmail,
    subject: "請求書の再送",
    body:    `請求書はこちらからダウンロードできます: ${downloadUrl}`,
  });

  console.log(`ページ ${invoicePageNumber} を ${recipientEmail} に送信しました`);
  return downloadUrl;
}

証明書の一括生成から個別ファイルを抽出

一括生成した証明書PDFを1ページ1ファイルに分割します。

import asyncio
import httpx

async def split_certificate_pages(
    batch_filename: str,
    total_pages: int,
    api_key: str,
    concurrency: int = 5,
) -> list[str]:
    """
    一括証明書PDFを個別ファイルに分割し、ダウンロードURLのリストを返す
    """
    sem = asyncio.Semaphore(concurrency)

    async def split_one(page_num: int) -> str:
        async with sem:
            async with httpx.AsyncClient() as client:
                response = await client.post(
                    "https://pdf.funbrew.cloud/api/pdf/split",
                    headers={"Authorization": f"Bearer {api_key}"},
                    json={
                        "filename":         batch_filename,
                        "pages":            str(page_num),
                        "expiration_hours": 168,
                        "max_downloads":    3,
                    },
                    timeout=60,
                )
                response.raise_for_status()
                data = response.json()["data"]
                print(f"  ページ {page_num}: {data['download_url']}")
                return data["download_url"]

    tasks = [split_one(p) for p in range(1, total_pages + 1)]
    return await asyncio.gather(*tasks)


# 一括証明書PDF生成後に個別ファイルへ分割
download_urls = asyncio.run(
    split_certificate_pages(
        batch_filename="batch-certificates-abc123.pdf",
        total_pages=50,
        api_key=os.environ["FUNBREW_PDF_API_KEY"],
    )
)
print(f"{len(download_urls)} 件の個別証明書URLを生成しました")

分割・結合・圧縮の組み合わせ

分割・結合・圧縮の各エンドポイントを組み合わせることで、高度なドキュメントワークフローを構築できます。

分割してから圧縮

大きなPDFの特定部分を抽出し、メール配信に向けて圧縮します。

# Step 1: 大きなPDFから5〜15ページを分割
split_result = split_pdf(
    filename="large-report-abc.pdf",
    pages="5-15",
    expiration_hours=2,  # 中間ファイルは短期有効
)
split_filename = split_result["data"]["filename"]

# Step 2: 抽出したセクションを圧縮
compress_response = httpx.post(
    f"{BASE_URL}/api/pdf/compress",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "filename": split_filename,
        "quality":  "low",         # メール配信向けに最大圧縮
        "expiration_hours": 48,
    },
    timeout=60,
)
compress_response.raise_for_status()
final_url = compress_response.json()["data"]["download_url"]
print("圧縮済み抽出ファイル:", final_url)

よくある失敗と対処法

ファイルが見つからない(422)

{
  "success": false,
  "message": "File not found or not owned by your company: report-abc.pdf"
}

主な原因:

  1. 生成時と別のAPIキーを使用 — ファイルはcompany(APIキーに紐付く企業)単位でスコープされています。生成と分割で同じAPIキーを使用してください。
  2. 分割前にファイルが期限切れexpiration_hoursが経過するとファイルは削除されます。生成時に長めの有効期限を設定するか、生成直後に分割を実行してください。
  3. filenameのタイポ — 生成エンドポイントが返したdata.filenameの値をそのまま使用してください。

無効なページ範囲(422)

{
  "success": false,
  "message": "Invalid page range. The PDF has 10 pages."
}

ページ番号は1以上・PDFの総ページ数以内でなければなりません。また範囲指定は昇順(1-5は有効・5-1は無効)である必要があります。動的なページ番号を使用する場合は、事前に総ページ数を確認してください。

中間ファイルの期限切れ

分割→圧縮→結合のようにエンドポイントを連鎖させる場合、パイプラインの途中で中間ファイルが期限切れになることがあります。直後のステップがすぐ実行されるなら短い有効期限で十分です。

# 圧縮ステップがすぐに実行されるなら1時間で十分
split_result = split_pdf(filename, pages="1-5", expiration_hours=1)
# 直ちに次のステップへ
compress_result = compress_pdf(split_result["data"]["filename"])

関連APIとガイド

操作 エンドポイント ガイド
PDF生成 POST /api/pdf/generate PDF APIクイックスタート
PDF結合 POST /api/pdf/merge PDF結合API完全ガイド
PDF圧縮 POST /api/pdf/compress PDF圧縮API完全ガイド
ファイルダウンロード GET /api/pdf/download/{filename} APIドキュメント
PDF操作の概要 PDF操作ガイド

完全なAPIリファレンスはAPIドキュメントを参照してください。コードを書く前に実際に試したい場合はFUNBREW PDF Playgroundをご利用ください。証明書の一括抽出などの実際のユースケースはUse Casesで紹介しています。


まとめ

FUNBREW PDFのPDF分割APIのポイントは以下のとおりです。

  1. 2ステップのワークフロー: POST /api/pdf/generateでPDFを生成し、返されたfilenameを使ってPOST /api/pdf/splitで分割する。
  2. 柔軟なページ指定: 範囲(1-5)と個別ページ(7,9)を組み合わせた指定が可能。
  3. ページの重複は自動除去: 同じページ番号を複数回指定しても1回だけ出力される。
  4. 無効な範囲は422エラー: PDFの総ページ数を超えたページ番号があるとリクエスト全体が失敗する。動的な入力値を使う場合はバリデーションを事前に実施する。
  5. 結合・圧縮と組み合わせ可能: 分割後に圧縮してメール配信、または分割したページを結合してカスタム資料を作成できる。
  6. SaaSエディション専用: プランでpdf.feature:splitフィーチャーゲートが有効である必要がある。
Powered by FUNBREW PDF