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

# PDF Compress

> Compress a PDF with ready-to-use presets or advanced image and font controls.

<Note>
  **Try it live:** [PDF Compress → API Tester](/api-tester/pdf-compress) — send a real request from your browser.
</Note>

## `POST /v2/pdf/compress`

This is the current PDF compression endpoint. The legacy PDF Optimize V1 endpoint is deprecated.

## Quick start

A request containing only `url` automatically uses the `medium` preset, so you can start compressing immediately.

<Tip>
  Start with the `medium` preset for balanced compression. Choose another preset only when you need lighter or stronger compression.
</Tip>

<Note>To see the request size limits, please refer to the [Request Size Limits](/api/url-input-and-request-limits#pdf-co-request-size).</Note>

<div id="compression-level" />

## Choose a preset

For most files, choose one of four `compressionLevel` values. You do not need to understand the advanced configuration to use them.

* `low` — Light compression with the highest image resolution.
* `medium` — Balanced compression based on Adobe Standard resolution targets.
* `high` — Stronger compression with lower image resolution.
* `aggressive` — The smallest images and strongest compression of the four presets.

You can also set `colorQuality` from `1` to `100`. Higher values preserve more color and grayscale image quality and usually produce larger files. The default is `60`.

```json Preset request body theme={null}
{
  "url": "https://pdfco-test-files.s3.us-west-2.amazonaws.com/pdf-compress/sample.pdf",
  "compressionLevel": "high"
}
```

<Accordion title="Preset resolution targets">
  Exact PPI thresholds used by each preset.

  | Preset       | Color and grayscale        | Monochrome                 |
  | ------------ | -------------------------- | -------------------------- |
  | `low`        | 200 PPI when above 300 PPI | 400 PPI when above 600 PPI |
  | `medium`     | 150 PPI when above 225 PPI | 300 PPI when above 450 PPI |
  | `high`       | 127 PPI when above 172 PPI | 180 PPI when above 270 PPI |
  | `aggressive` | 96 PPI when above 120 PPI  | 100 PPI when above 150 PPI |
</Accordion>

## Request body

<Note>Attributes are case-sensitive and should be inside JSON for POST request. for example: `{ "url": "https://example.com/file1.pdf" }` There are no query parameters.</Note>

<ParamField body="url" type="string" required>
  URL of the source PDF. The endpoint processes the complete document. The URL must be reachable by PDF.co — see [supported file sources](/api/url-input-and-request-limits#supported-file-sources). For a protected source location, provide a temporary or presigned URL.
</ParamField>

<ParamField body="compressionLevel" type="string">
  Ready-to-use compression profile: `low`, `medium`, `high`, or `aggressive`. When omitted, `medium` is applied. Presets also enable font subsetting and font stream compression.
</ParamField>

<ParamField body="colorQuality" type="integer" default="60">
  JPEG quality for color and grayscale images, from `1` for the smallest file and lowest quality to `100` for the highest quality. It can be used with or without `compressionLevel`.
</ParamField>

<Note>
  The previously published `compression_level` and `color_quality` names are still accepted for backward compatibility. Use `compressionLevel` and `colorQuality` in new integrations.
</Note>

<ParamField body="password" type="string">
  Password for opening an encrypted source PDF. Omit it for an unprotected PDF.
</ParamField>

<ParamField body="async" type="boolean" default="false">
  Set to `true` for large or long-running documents. The initial response includes `jobId`; use the [Background Job Check endpoint](/api/job-check) to retrieve the final status. Also see [Webhooks & Callbacks](/api/webhooks).
</ParamField>

<ParamField body="callback" type="string">
  Callback URL notified when an asynchronous job finishes. Use it with `async: true`.
</ParamField>

<ParamField body="name" type="string">
  Output file name. The endpoint appends `.pdf` when needed. If omitted, it derives the name from `url` when possible.
</ParamField>

<ParamField body="expiration" type="integer" default="60">
  Number of minutes before the temporary output URL expires. After this period, generated files are deleted from [PDF.co Temporary Files Storage](/api/file-upload/overview). The maximum retention depends on your subscription plan. To store permanent input files (e.g. re-usable images, pdf templates, documents) consider using [PDF.co Built-In Files Storage](https://app.pdf.co/tools/files).
</ParamField>

<Note>
  The current V2 Compress implementation does not use `httpusername` or `httppassword`. The old `profiles.outputDataFormat` and `profiles.JPEGQuality` descriptions also do not apply to this endpoint.
</Note>

<div id="config" />

## Advanced configuration

Use `config` only when a preset is not enough. It is a partial object: you only send the values you want to override.

1. PDF.co starts with the standard configuration.
2. It applies `medium`, or the selected `compressionLevel`.
3. It applies `colorQuality`, when supplied.
4. It deep-merges `config` as the final override.

<Accordion title="How presets and config work together">
  | Request                         | Image resolution                         | Color and grayscale encoding            | Fonts                                 |
  | ------------------------------- | ---------------------------------------- | --------------------------------------- | ------------------------------------- |
  | `url` only                      | Medium targets                           | JPEG, quality 60                        | Subset and compress                   |
  | `compressionLevel`              | Targets from the selected preset         | JPEG, quality 60 unless overridden      | Subset and compress                   |
  | `colorQuality` only             | Medium targets                           | JPEG at the selected quality            | Subset and compress                   |
  | `config` only                   | Medium targets with explicit overrides   | JPEG, quality 60 unless overridden      | Subset and compress unless overridden |
  | Preset or quality plus `config` | Selected targets with explicit overrides | Selected JPEG quality unless overridden | Subset and compress unless overridden |

  <Tip>
    Specify the narrowest override you need. Every omitted value continues to come from the base configuration, or from the preset and `colorQuality` you selected.
  </Tip>
</Accordion>

<Accordion title="Preset equivalents in config">
  Each preset resolves to an effective configuration for the first compression pass. All presets use JPEG quality 60 for color and grayscale images, CCITT Group 4 for monochrome images, font subsetting and compression, and garbage collection level 4. Their resolution targets differ per the **Preset resolution targets** table above.

  To represent another preset, change the four PPI values using that table. To represent a different `colorQuality`, change both JPEG `quality` values.

  The effective values produced by the `medium` preset:

  ```json theme={null}
  {
    "config": {
      "images": {
        "color": {
          "skip": false,
          "downsample": {
            "skip": false,
            "downsample_ppi": 150,
            "threshold_ppi": 225
          },
          "compression": {
            "skip": false,
            "compression_format": "jpeg",
            "compression_params": { "quality": 60 }
          }
        },
        "grayscale": {
          "skip": false,
          "downsample": {
            "skip": false,
            "downsample_ppi": 150,
            "threshold_ppi": 225
          },
          "compression": {
            "skip": false,
            "compression_format": "jpeg",
            "compression_params": { "quality": 60 }
          }
        },
        "monochrome": {
          "skip": false,
          "downsample": {
            "skip": false,
            "downsample_ppi": 300,
            "threshold_ppi": 450
          },
          "compression": {
            "skip": false,
            "compression_format": "ccitt_g4",
            "compression_params": {}
          }
        }
      },
      "fonts": {
        "subset": true,
        "compress": true
      },
      "save": {
        "garbage": 4
      }
    }
  }
  ```

  <Note>
    Use `compressionLevel` when a preset already fits your needs. The configuration above produces the same first pass, but explicit `config` values are preserved during the standard fallback retry, so manually copying a complete preset can change fallback behavior.
  </Note>
</Accordion>

### Common config recipes

These examples show only the request fields relevant to the change. Add the same fields to the request body together with `url`.

<Accordion title="Use custom resolution targets">
  Keep the base encoding and fonts, but retain more color and grayscale detail. Images are reduced to 200 PPI only when their effective resolution is above 300 PPI. Monochrome images keep the base settings.

  ```json theme={null}
  {
    "config": {
      "images": {
        "color": {
          "downsample": {
            "downsample_ppi": 200,
            "threshold_ppi": 300
          }
        },
        "grayscale": {
          "downsample": {
            "downsample_ppi": 200,
            "threshold_ppi": 300
          }
        }
      }
    }
  }
  ```
</Accordion>

<Accordion title="Re-encode images without resizing them">
  Preserve pixel dimensions while still applying the selected image encoding. The default preset encoding is JPEG.

  ```json theme={null}
  {
    "config": {
      "images": {
        "color": { "downsample": { "skip": true } },
        "grayscale": { "downsample": { "skip": true } }
      }
    }
  }
  ```
</Accordion>

<Accordion title="Leave monochrome images unchanged">
  Do not downsample or re-encode monochrome images.

  ```json theme={null}
  {
    "config": {
      "images": {
        "monochrome": { "skip": true }
      }
    }
  }
  ```
</Accordion>

<Accordion title="Combine a preset, quality, and config">
  Start with `high`, preserve more image quality, and make two exceptions. This keeps the `high` resolution targets, uses color quality 90, leaves monochrome images unchanged, and disables font subsetting. Font stream compression remains enabled.

  ```json theme={null}
  {
    "compressionLevel": "high",
    "colorQuality": 90,
    "config": {
      "images": {
        "monochrome": { "skip": true }
      },
      "fonts": {
        "subset": false
      }
    }
  }
  ```
</Accordion>

### What each skip setting does

| Setting              | What it skips                                          | What can still run                                           |
| -------------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| `images.<type>.skip` | All optimization for that image type                   | Other image types and fonts                                  |
| `downsample.skip`    | Resolution reduction                                   | Image re-encoding                                            |
| `compression.skip`   | The selected JPEG, JPEG2000, CCITT, or ZIP compression | Downsampling; resized image data may still be written as PNG |

<ParamField body="config" type="object">
  Advanced image, font, and save controls.

  <Expandable title="config properties">
    <ParamField body="images" type="object">
      Settings for color, grayscale, and monochrome images.

      <Expandable title="color / grayscale / monochrome">
        <ParamField body="color" type="object">
          `color`, `grayscale` & `monochrome` all use the same object schema:

          <Expandable title="properties">
            <ParamField body="skip" type="boolean" default="false">
              Skip both downsampling and re-encoding for this image type.
            </ParamField>

            <ParamField body="downsample" type="object">
              Control resolution reduction.

              <Expandable title="downsample properties">
                <ParamField body="skip" type="boolean" default="false">
                  Preserve image dimensions while allowing re-encoding.
                </ParamField>

                <ParamField body="downsample_ppi" type="integer" default="150">
                  Target resolution when the image is above `threshold_ppi`. Default is `150` for `color` and `grayscale`, `300` for `monochrome`.
                </ParamField>

                <ParamField body="threshold_ppi" type="integer" default="225">
                  Minimum effective resolution that triggers downsampling. Default is `225` for `color` and `grayscale`, `450` for `monochrome`.
                </ParamField>
              </Expandable>
            </ParamField>

            <ParamField body="compression" type="object">
              Control image re-encoding.

              <Expandable title="compression properties">
                <ParamField body="skip" type="boolean" default="false">
                  Skip the selected compression format while allowing downsampling. Resized image data may still be written as PNG.
                </ParamField>

                <ParamField body="compression_format" type="string">
                  `jpeg`, `jpeg2000`, `ccitt_g4`, `ccitt_g3`, or `zip`. Use CCITT for monochrome images.
                </ParamField>

                <ParamField body="compression_params" type="object">
                  JPEG or JPEG2000 quality settings.

                  <Expandable title="compression_params properties">
                    <ParamField body="quality" type="integer">
                      JPEG quality from `1` to `100`. This field is used only when `compression_format` is `jpeg`.
                    </ParamField>

                    <ParamField body="quality_mode" type="string" default="rates">
                      JPEG2000 mode: `rates` or `dB`.
                    </ParamField>

                    <ParamField body="quality_layers" type="number[]">
                      JPEG2000 quality layers. When supplied directly without valid layers, the fallback is `[30]` for `rates` or `[38.0, 34.0, 30.0]` for `dB`.
                    </ParamField>

                    <ParamField body="irreversible" type="boolean" default="false">
                      JPEG2000 only. Set to `true` to use irreversible lossy encoding. The default `false` uses reversible encoding.
                    </ParamField>

                    <ParamField body="mct" type="integer" default="0">
                      JPEG2000 only. Set to `1` to enable the multi-component transform for RGB images, or `0` to disable it.
                    </ParamField>
                  </Expandable>
                </ParamField>
              </Expandable>
            </ParamField>
          </Expandable>
        </ParamField>

        <ParamField body="grayscale" type="object">
          Same object schema as `color`.
        </ParamField>

        <ParamField body="monochrome" type="object">
          Same object schema as `color`.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="fonts" type="object">
      Font subsetting and stream compression.

      <Expandable title="fonts properties">
        <ParamField body="subset" type="boolean">
          Remove unused glyphs from fonts that can be subset.
        </ParamField>

        <ParamField body="compress" type="boolean">
          Compress font streams while saving the PDF.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="save" type="object">
      PDF cleanup settings.

      <Expandable title="save properties">
        <ParamField body="garbage" type="integer" default="4">
          Garbage collection level from `0` to `4`.

          * `0` — none
          * `1` — remove unused objects
          * `2` — compact xref
          * `3` — merge duplicate objects
          * `4` — detect duplicate stream content
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<Accordion title="Standard fallback config">
  Fallback base used when the first compression pass is not smaller. A request containing only `url` uses `medium` for its first pass; this fallback has the same image settings but does not subset or compress fonts.

  ```json theme={null}
  {
    "images": {
      "color": {
        "skip": false,
        "downsample": { "skip": false, "downsample_ppi": 150, "threshold_ppi": 225 },
        "compression": { "skip": false, "compression_format": "jpeg", "compression_params": { "quality": 60 } }
      },
      "grayscale": {
        "skip": false,
        "downsample": { "skip": false, "downsample_ppi": 150, "threshold_ppi": 225 },
        "compression": { "skip": false, "compression_format": "jpeg", "compression_params": { "quality": 60 } }
      },
      "monochrome": {
        "skip": false,
        "downsample": { "skip": false, "downsample_ppi": 300, "threshold_ppi": 450 },
        "compression": { "skip": false, "compression_format": "ccitt_g4", "compression_params": {} }
      }
    },
    "fonts": { "subset": false, "compress": false },
    "save": { "garbage": 4 }
  }
  ```
</Accordion>

## Behavior notes

* Compression results depend on the source PDF. Presets do not promise a fixed reduction, and two levels can produce the same file size.
* When the first compression pass does not produce a smaller PDF, PDF.co retries with the standard configuration while preserving explicit `config` overrides. If the retry is also not smaller, it returns the original PDF.
* Each re-encoded image is kept only when its new stream is smaller. If effective PPI cannot be determined, downsampling is skipped but re-encoding can still run at the original dimensions.

## Responses

A synchronous success returns the final temporary output URL.

```json Synchronous response theme={null}
{
  "pageCount": 2,
  "error": false,
  "status": 200,
  "credits": 70,
  "remainingCredits": 999860,
  "duration": 8768,
  "url": "https://pdf-temp-files.s3.amazonaws.com/example/sample.pdf",
  "name": "sample.pdf",
  "outputLinkValidTill": "2026-08-08T12:00:00+00:00"
}
```

| Field                 | Type    | Description                                                                                    |
| --------------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `pageCount`           | integer | Number of pages in the output PDF.                                                             |
| `error`               | boolean | `false` for a successful request.                                                              |
| `status`              | integer | PDF.co status code. Success returns `200`. See [Response Codes](/api/response-codes).          |
| `credits`             | integer | Credits consumed by the request.                                                               |
| `remainingCredits`    | integer | Credits remaining for the account.                                                             |
| `duration`            | integer | Processing duration in milliseconds.                                                           |
| `url`                 | string  | Temporary URL of the result. With size protection, it can point to a copy of the original PDF. |
| `name`                | string  | Output file name.                                                                              |
| `outputLinkValidTill` | string  | UTC timestamp when the temporary URL expires.                                                  |
| `jobId`               | string  | Present in the initial response when `async` is `true`.                                        |

An asynchronous request returns a `jobId` and a reserved URL that should be used only after the job succeeds. Poll it via [Background Job Check](/api/job-check). Errors return `error: true` with a status code and message. See the response examples and [Response Codes](/api/response-codes).

<Note>
  **Inconsistent URL Encoding in cURL Output:** When using cURL to make API requests, the output JSON may show URL characters encoded as Unicode escape sequences. For example, the ampersand character (`&`) may appear as `\u0026` in the cURL output. This is normal JSON encoding behavior and does not affect the validity of the URL. The URL will function correctly when used, as JSON parsers automatically decode these escape sequences. If you're parsing the response programmatically, your JSON parser will handle this conversion automatically.
</Note>

<div id="code-samples" />

<Panel>
  **Sample request**

  <RequestExample dropdown>
    ```bash cURL theme={null}
    curl --request POST \
      --url 'https://api.pdf.co/v2/pdf/compress' \
      --header 'Content-Type: application/json' \
      --header 'x-api-key: YOUR_API_KEY' \
      --data '{
        "url": "https://pdfco-test-files.s3.us-west-2.amazonaws.com/pdf-compress/sample.pdf"
      }'
    ```

    ```javascript Node.js theme={null}
    const response = await fetch(
      "https://api.pdf.co/v2/pdf/compress",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-api-key": "YOUR_API_KEY",
        },
        body: JSON.stringify({
          url: "https://pdfco-test-files.s3.us-west-2.amazonaws.com/pdf-compress/sample.pdf",
          compressionLevel: "medium",
          colorQuality: 60,
          name: "compressed.pdf",
        }),
      }
    );
    const result = await response.json();
    if (!response.ok || result.error) {
      throw new Error(result.message ?? "PDF.co request failed");
    }
    console.log(result.url);
    ```

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

    response = requests.post(
        "https://api.pdf.co/v2/pdf/compress",
        headers={"x-api-key": "YOUR_API_KEY"},
        json={
            "url": "https://pdfco-test-files.s3.us-west-2.amazonaws.com/pdf-compress/sample.pdf",
            "compressionLevel": "medium",
            "colorQuality": 60,
            "name": "compressed.pdf",
        },
    )
    result = response.json()
    if result.get("error"):
        raise RuntimeError(result.get("message", "PDF.co request failed"))
    print(result["url"])
    ```

    ```csharp C# theme={null}
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Text;
    using System.Text.Json;

    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY");

    var payload = JsonSerializer.Serialize(new Dictionary<string, object>
    {
        ["url"] = "https://pdfco-test-files.s3.us-west-2.amazonaws.com/pdf-compress/sample.pdf",
        ["compressionLevel"] = "medium",
        ["colorQuality"] = 60,
        ["name"] = "compressed.pdf",
    });

    var response = await client.PostAsync(
        "https://api.pdf.co/v2/pdf/compress",
        new StringContent(payload, Encoding.UTF8, "application/json")
    );
    var json = await response.Content.ReadAsStringAsync();
    Console.WriteLine(json);
    ```

    ```java Java theme={null}
    OkHttpClient client = new OkHttpClient();

    String jsonPayload = "{\"url\": \"https://pdfco-test-files.s3.us-west-2.amazonaws.com/pdf-compress/sample.pdf\", \"compressionLevel\": \"medium\", \"colorQuality\": 60, \"name\": \"compressed.pdf\"}";

    Request request = new Request.Builder()
        .url("https://api.pdf.co/v2/pdf/compress")
        .addHeader("x-api-key", "YOUR_API_KEY")
        .addHeader("Content-Type", "application/json")
        .post(RequestBody.create(MediaType.parse("application/json"), jsonPayload))
        .build();

    try (Response response = client.newCall(request).execute()) {
        System.out.println(response.body().string());
    }
    ```

    ```php PHP theme={null}
    <?php
    $payload = json_encode([
        "url" => "https://pdfco-test-files.s3.us-west-2.amazonaws.com/pdf-compress/sample.pdf",
        "compressionLevel" => "medium",
        "colorQuality" => 60,
        "name" => "compressed.pdf",
    ]);

    $curl = curl_init("https://api.pdf.co/v2/pdf/compress");
    curl_setopt_array($curl, [
        CURLOPT_HTTPHEADER => [
            "x-api-key: YOUR_API_KEY",
            "Content-Type: application/json",
        ],
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS => $payload,
    ]);

    echo curl_exec($curl);
    curl_close($curl);
    ```
  </RequestExample>

  <ResponseExample>
    ```json 200 theme={null}
    {
      "pageCount": 2,
      "error": false,
      "status": 200,
      "credits": 70,
      "remainingCredits": 999860,
      "duration": 8768,
      "url": "https://pdf-temp-files.s3.amazonaws.com/example/sample.pdf",
      "name": "sample.pdf",
      "outputLinkValidTill": "2026-08-08T12:00:00+00:00"
    }
    ```

    ```json 400 theme={null}
    {
      "error": true,
      "status": 400,
      "message": "Bad request. Typically due to bad input parameters or unreachable input URLs (e.g., access restrictions like login or password)."
    }
    ```

    ```json 401 theme={null}
    {
      "error": true,
      "status": 401,
      "message": "Unauthorized. Authentication is required and has failed or has not yet been provided."
    }
    ```

    ```json 402 theme={null}
    {
      "error": true,
      "status": 402,
      "message": "Not enough credits."
    }
    ```

    ```json 403 theme={null}
    {
      "error": true,
      "status": 403,
      "message": "Access forbidden for input URL."
    }
    ```

    ```json 404 theme={null}
    {
      "error": true,
      "status": 404,
      "message": "The requested resource could not be found."
    }
    ```

    ```json 408 theme={null}
    {
      "error": true,
      "status": 408,
      "message": "The server timed out waiting for the request."
    }
    ```

    ```json 429 theme={null}
    {
      "error": true,
      "status": 429,
      "message": "Too many requests in a given time period."
    }
    ```

    ```json 441 theme={null}
    {
      "error": true,
      "status": 441,
      "message": "Invalid Password. Password protected document."
    }
    ```

    ```json 442 theme={null}
    {
      "error": true,
      "status": 442,
      "message": "Input document is damaged or of incorrect type."
    }
    ```

    ```json 443 theme={null}
    {
      "error": true,
      "status": 443,
      "message": "Permissions. The operation is prohibited by document security settings."
    }
    ```

    ```json 444 theme={null}
    {
      "error": true,
      "status": 444,
      "message": "Profiles parsing error. Please ensure that the configuration is supported."
    }
    ```

    ```json 445 theme={null}
    {
      "error": true,
      "status": 445,
      "message": "Timeout error. For large documents, use asynchronous mode (async=true) and check status via /job/check."
    }
    ```

    ```json 446 theme={null}
    {
      "error": true,
      "status": 446,
      "message": "Some files required for conversion are missing."
    }
    ```

    ```json 447 theme={null}
    {
      "error": true,
      "status": 447,
      "message": "Invalid template."
    }
    ```

    ```json 448 theme={null}
    {
      "error": true,
      "status": 448,
      "message": "Invalid URL or HTML. Ensure the provided URL is valid and accessible."
    }
    ```

    ```json 449 theme={null}
    {
      "error": true,
      "status": 449,
      "message": "Invalid index range. Page index is out of range."
    }
    ```

    ```json 450 theme={null}
    {
      "error": true,
      "status": 450,
      "message": "Invalid page range specified."
    }
    ```

    ```json 452 theme={null}
    {
      "error": true,
      "status": 452,
      "message": "Invalid URL."
    }
    ```

    ```json 454 theme={null}
    {
      "error": true,
      "status": 454,
      "message": "Invalid parameters."
    }
    ```

    ```json 500 theme={null}
    {
      "error": true,
      "status": 500,
      "message": "Something went wrong. Please try again or contact support."
    }
    ```
  </ResponseExample>
</Panel>
