> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pdfnoodle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Merge PDFs

> Combine two or more PDF files into a single document. The PDFs are merged in the order they are provided.

<Note>
  This tool requires publicly accessible PDF URLs. If your files are stored locally or in memory, you can upload them to our temporary bucket first using the [Get Signed Upload URL](/api-reference/tools/get-signed-upload-url) endpoint.
</Note>

POST [https://api.pdfnoodle.com/v1/tools/merge-pdfs](https://api.pdfnoodle.com/v1/tools/merge-pdfs)

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl --location 'https://api.pdfnoodle.com/v1/tools/merge-pdfs' \
  --header 'Authorization: Bearer pdfnoodle_api_123456789' \
  --header 'Content-Type: application/json' \
  --data '{
      "urls": [
          "https://example.com/document-1.pdf",
          "https://example.com/document-2.pdf",
          "https://example.com/document-3.pdf"
      ],
      "finalFilename": "merged-report.pdf",
      "expiration": 3600
  }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.pdfnoodle.com/v1/tools/merge-pdfs", {
    method: "POST",
    headers: {
      Authorization: "Bearer pdfnoodle_api_123456789",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      urls: [
        "https://example.com/document-1.pdf",
        "https://example.com/document-2.pdf",
        "https://example.com/document-3.pdf",
      ],
      finalFilename: "merged-report.pdf",
      expiration: 3600,
    }),
  });

  const result = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.pdfnoodle.com/v1/tools/merge-pdfs',
      headers={
          'Authorization': 'Bearer pdfnoodle_api_123456789',
          'Content-Type': 'application/json'
      },
      json={
          'urls': [
              'https://example.com/document-1.pdf',
              'https://example.com/document-2.pdf',
              'https://example.com/document-3.pdf'
          ],
          'finalFilename': 'merged-report.pdf',
          'expiration': 3600
      }
  )

  result = response.json()
  ```
</CodeGroup>

## Response

<CodeGroup>
  ```json Success (200 OK) theme={null}
  {
    "status": "SUCCESS",
    "url": "https://s3.amazonaws.com/...",
    "fileName": "merged-report.pdf",
    "urlValidUntil": "2025-01-01T02:00:00.000Z"
  }
  ```

  ```json Validation Error (400 Bad Request) theme={null}
  {
    "message": "urls is at least 2 PDF URLs are required"
  }
  ```
</CodeGroup>

The response contains a `url` pointing to the merged PDF file. While the URL in the response expires after the time specified in `expiration` (default: 1 hour), the file itself is stored persistently and can be downloaded at any time from the [dashboard logs](https://app.pdfnoodle.com/logs).

<Info>
  PDFs are merged in the exact order they appear in the `urls` array. Make sure to order them correctly.
</Info>

## Operation Tracking

Each merge operation creates a record in your dashboard logs, allowing you to:

* View all past merge operations with their status and metadata
* Re-download merged files at any time via the logs table
* Track usage across your team

<Note>
  **Business and Scale plans**: Merge operations are unlimited and do not count toward your PDF generation quota.

  **Starter plans**: Merge operations count toward your total volume quota.
</Note>

## Async Mode

By default, the request waits for the merge to complete before returning a response. If you set `async: true`, the endpoint returns immediately with a `requestId` and `statusUrl` that you can use to poll for the result.

```json Async Response (200 OK) theme={null}
{
  "requestId": "pdfnoodle_request_123456789",
  "statusUrl": "https://api.pdfnoodle.com/v1/tools/status/pdfnoodle_request_123456789",
  "message": "The tool is being executed asynchronously. Check the status using the status URL."
}
```

Use the [Get Tool Status](/api-reference/tools/get-tool-status) endpoint to check the result.

### Request Timeout (>30 seconds)

<Warning>
  Even without `async: true`, if the operation takes more than 30 seconds it will automatically be processed in the background.
</Warning>

If the merge takes longer than 30 seconds, you'll receive a `202 Accepted` response with a `requestId` and `statusUrl` to poll for the result:

```json Timeout (202 Accepted) theme={null}
{
  "requestId": "pdfnoodle_request_123456789",
  "statusUrl": "https://api.pdfnoodle.com/v1/tools/status/pdfnoodle_request_123456789",
  "message": "Couldn't complete the operation within 30 seconds, it is being processed asynchronously. Check the status using the status URL."
}
```

## Parameters

<ParamField body="urls" type="string[]" required>
  An array of URLs pointing to the PDF files you want to merge. A minimum of 2
  URLs is required. Each URL must be a valid, publicly accessible URL.
</ParamField>

<ParamField body="finalFilename" type="string">
  The desired filename for the merged PDF. Must end with `.pdf`. If not
  provided, a random filename will be generated.
</ParamField>

<ParamField body="expiration" type="number" default="3600">
  Number of seconds that the generated signed URL will take to expire. Must be
  between 60 (1 minute) and 604800 (7 days). Default: 3600 (1 hour).
</ParamField>

<ParamField body="async" type="boolean" default="false">
  If `true`, the request returns immediately with a `requestId` and `statusUrl`
  instead of waiting for the operation to complete. You can then poll the
  [Get Tool Status](/api-reference/tools/get-tool-status) endpoint to check
  when the result is ready.
</ParamField>
