PDF Blocks
PricingSupport
Start Free
Go to Page

Split a PDF into page groups

Split a PDF into caller-defined page groups — each group (for example 2..8,29;1) becomes one output PDF.

Split a PDF document into page groups that you define — each group becomes one output PDF, in written order. Within a group, pages and ranges follow ordered page syntax, so they come out exactly as listed. This action returns multiple documents, packaged into a single response according to the Accept header. The API is stateless: your document is processed in-region and never stored.

Endpoint

POST
/v1/split_by_groups

Available in every region. See Regions & data residency for routing and data residency.

Region URL
Global https://api.pdfblocks.com/v1/split_by_groups
United States https://us.api.pdfblocks.com/v1/split_by_groups
US HIPAA https://hipaa.api.pdfblocks.com/v1/split_by_groups
European Union https://eu.api.pdfblocks.com/v1/split_by_groups
United Kingdom https://uk.api.pdfblocks.com/v1/split_by_groups
Canada https://ca.api.pdfblocks.com/v1/split_by_groups
Australia https://au.api.pdfblocks.com/v1/split_by_groups
Japan https://jp.api.pdfblocks.com/v1/split_by_groups
India https://in.api.pdfblocks.com/v1/split_by_groups
Brazil https://br.api.pdfblocks.com/v1/split_by_groups

Authentication

Authenticate every request with your secret API key in the X-API-Key header, over HTTPS. Create and manage keys from the dashboard. See Authentication for details.

Request

The endpoint accepts a multipart/form-data request body.

filefilerequired

The input PDF document.

groupsstringrequired

The page groups to produce, following the page groups syntax. Groups are separated by ;; within a group, pages and ranges are separated by , and follow ordered rules. Each group becomes one output PDF, in written order. For example, 2..8,29;1 produces two PDFs: pages 2 through 8 then 29, and page 1.

Page groups

Groups are separated by ;. Within each group, pages and ranges follow ordered syntax: order and repeats matter, a page named twice is emitted twice, and a range may run backwards. Each group becomes one output PDF, emitted in the order the groups are written.

For example, 2..8,29;1 produces two documents:

Group Output PDF Pages
2..8,29 00001.pdf Pages 2, 3, 4, 5, 6, 7, 8, then 29
1 00002.pdf Page 1

Within a group, groups shares the ordered semantics of page_order. See Selecting pages for the complete reference.

Examples

Split a document into two PDFs — pages 2–8 then 29, and page 1:

cURLbash
curl https://api.pdfblocks.com/v1/split_by_groups \
  -H 'X-API-Key: your_api_key' \
  -F file=@input.pdf \
  -F groups='2..8,29;1' \
  -o parts.zip
Pythonpython
# pip install requests
import requests

with open('input.pdf', 'rb') as file:
    response = requests.post(
        'https://api.pdfblocks.com/v1/split_by_groups',
        headers={'X-API-Key': 'your_api_key'},
        files={'file': file},
        data={'groups': '2..8,29;1'},
    )

response.raise_for_status()
with open('parts.zip', 'wb') as output:
    output.write(response.content)
Node.jsjavascript
// Node.js 18+
import { readFile, writeFile } from 'node:fs/promises';

const body = new FormData();
body.set('file', new Blob([await readFile('input.pdf')]), 'input.pdf');
body.set('groups', '2..8,29;1');

const response = await fetch('https://api.pdfblocks.com/v1/split_by_groups', {
  method: 'POST',
  headers: { 'X-API-Key': 'your_api_key' },
  body,
});

if (!response.ok) throw new Error(`Request failed: ${response.status}`);
await writeFile('parts.zip', Buffer.from(await response.arrayBuffer()));
PHPphp
<?php
$ch = curl_init('https://api.pdfblocks.com/v1/split_by_groups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['X-API-Key: your_api_key'],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile('input.pdf', 'application/pdf'),
        'groups' => '2..8,29;1',
    ],
]);

$zip = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) {
    file_put_contents('parts.zip', $zip);
}
Rubyruby
# gem install http
require 'http'

response = HTTP
  .headers('X-API-Key' => 'your_api_key')
  .post('https://api.pdfblocks.com/v1/split_by_groups', form: {
    file: HTTP::FormData::File.new('input.pdf'),
    groups: '2..8,29;1',
  })

File.write('parts.zip', response.body) if response.status.success?
Gogo
package main

import (
	"bytes"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func main() {
	var buf bytes.Buffer
	form := multipart.NewWriter(&buf)

	file, _ := os.Open("input.pdf")
	defer file.Close()
	part, _ := form.CreateFormFile("file", "input.pdf")
	io.Copy(part, file)

	form.WriteField("groups", "2..8,29;1")
	form.Close()

	req, _ := http.NewRequest("POST", "https://api.pdfblocks.com/v1/split_by_groups", &buf)
	req.Header.Set("Content-Type", form.FormDataContentType())
	req.Header.Set("X-API-Key", "your_api_key")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()

	out, _ := os.Create("parts.zip")
	defer out.Close()
	io.Copy(out, res.Body)
}
C#csharp
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");

using var form = new MultipartFormDataContent
{
    { new ByteArrayContent(File.ReadAllBytes("input.pdf")), "file", "input.pdf" },
    { new StringContent("2..8,29;1"), "groups" },
};

var response = await client.PostAsync(
    "https://api.pdfblocks.com/v1/split_by_groups", form);
response.EnsureSuccessStatusCode();
await File.WriteAllBytesAsync(
    "parts.zip", await response.Content.ReadAsByteArrayAsync());

Response

This action returns multiple documents, packaged into a single response according to the Accept request header — see Response formats & content negotiation for the full negotiation reference. With no Accept header, the default is a ZIP archive:

HTTP/1.1 200 OK
Content-Type: application/zip

Select the packaging with the Accept header:

Accept header Response
(none sent) application/zip — the default
application/zip A ZIP archive of the output PDFs
application/json A JSON envelope of base64-encoded documents
multipart/mixed One PDF per part
anything else 406 Not Acceptable

The API produces one output document per group, in the order the groups are written, named 00001.pdf, 00002.pdf, and so on.

For end-to-end code that calls a split action and unpacks each format — extracting the ZIP, decoding the JSON envelope, or reading the multipart parts — see the Splitting a PDF guide.

Errors

Failed requests return an application/problem+json body. The most common one for this endpoint is a 400, returned when groups is missing or malformed, or when it references a page that does not exist in the document — the errors object names each field:

{
  "type": "https://www.pdfblocks.com/docs/api/v1/error/400",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "groups": ["The groups field references a page that does not exist in the document."]
  }
}

If the Accept header matches none of application/zip, application/json, or multipart/mixed — for example Accept: application/pdf — the API responds with 406 Not Acceptable. Omit Accept to take the ZIP default, or request one of the supported media types. A missing or invalid X-API-Key returns a 401. See Errors for every status code and the full response shape.

Recipes

A common variation. Expand it to see the code in every language.

Receive the parts as a JSON envelope

Send Accept: application/json to get every part inline in one response, then base64-decode each entry’s content to a file named by its name:

cURLbash
curl https://api.pdfblocks.com/v1/split_by_groups \
  -H 'X-API-Key: your_api_key' \
  -H 'Accept: application/json' \
  -F file=@input.pdf \
  -F groups='2..8,29;1' \
  | jq -r '.documents[] | .name + " " + .content' \
  | while read -r name content; do
      echo "$content" | base64 --decode > "$name"
    done
Pythonpython
# pip install requests
import base64
import requests

with open('input.pdf', 'rb') as file:
    response = requests.post(
        'https://api.pdfblocks.com/v1/split_by_groups',
        headers={
            'X-API-Key': 'your_api_key',
            'Accept': 'application/json',
        },
        files={'file': file},
        data={'groups': '2..8,29;1'},
    )

response.raise_for_status()
for document in response.json()['documents']:
    with open(document['name'], 'wb') as output:
        output.write(base64.b64decode(document['content']))
Node.jsjavascript
// Node.js 18+
import { readFile, writeFile } from 'node:fs/promises';

const body = new FormData();
body.set('file', new Blob([await readFile('input.pdf')]), 'input.pdf');
body.set('groups', '2..8,29;1');

const response = await fetch('https://api.pdfblocks.com/v1/split_by_groups', {
  method: 'POST',
  headers: { 'X-API-Key': 'your_api_key', Accept: 'application/json' },
  body,
});

if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const { documents } = await response.json();
for (const doc of documents) {
  await writeFile(doc.name, Buffer.from(doc.content, 'base64'));
}
PHPphp
<?php
$ch = curl_init('https://api.pdfblocks.com/v1/split_by_groups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: your_api_key',
        'Accept: application/json',
    ],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile('input.pdf', 'application/pdf'),
        'groups' => '2..8,29;1',
    ],
]);

$body = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) {
    foreach (json_decode($body, true)['documents'] as $document) {
        file_put_contents($document['name'], base64_decode($document['content']));
    }
}
Rubyruby
# gem install http
require 'base64'
require 'http'
require 'json'

response = HTTP
  .headers('X-API-Key' => 'your_api_key', 'Accept' => 'application/json')
  .post('https://api.pdfblocks.com/v1/split_by_groups', form: {
    file: HTTP::FormData::File.new('input.pdf'),
    groups: '2..8,29;1',
  })

if response.status.success?
  JSON.parse(response.body)['documents'].each do |document|
    File.write(document['name'], Base64.decode64(document['content']))
  end
end
Gogo
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

func main() {
	var buf bytes.Buffer
	form := multipart.NewWriter(&buf)

	file, _ := os.Open("input.pdf")
	defer file.Close()
	part, _ := form.CreateFormFile("file", "input.pdf")
	io.Copy(part, file)

	form.WriteField("groups", "2..8,29;1")
	form.Close()

	req, _ := http.NewRequest("POST", "https://api.pdfblocks.com/v1/split_by_groups", &buf)
	req.Header.Set("Content-Type", form.FormDataContentType())
	req.Header.Set("X-API-Key", "your_api_key")
	req.Header.Set("Accept", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()

	var result struct {
		Documents []struct {
			Name    string `json:"name"`
			Content string `json:"content"`
		} `json:"documents"`
	}
	json.NewDecoder(res.Body).Decode(&result)

	for _, doc := range result.Documents {
		data, _ := base64.StdEncoding.DecodeString(doc.Content)
		os.WriteFile(doc.Name, data, 0644)
	}
}
C#csharp
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");
client.DefaultRequestHeaders.Add("Accept", "application/json");

using var form = new MultipartFormDataContent
{
    { new ByteArrayContent(File.ReadAllBytes("input.pdf")), "file", "input.pdf" },
    { new StringContent("2..8,29;1"), "groups" },
};

var response = await client.PostAsync(
    "https://api.pdfblocks.com/v1/split_by_groups", form);
response.EnsureSuccessStatusCode();

using var json = JsonDocument.Parse(
    await response.Content.ReadAsStringAsync());
foreach (var document in json.RootElement.GetProperty("documents").EnumerateArray())
{
    var name = document.GetProperty("name").GetString()!;
    var content = document.GetProperty("content").GetString()!;
    await File.WriteAllBytesAsync(name, Convert.FromBase64String(content));
}