> ## 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` uses the standard configuration — the same image optimization as Adobe Acrobat Pro — 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 `compression_level` 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 `color_quality` from `1` to `100`. Higher values preserve more color and grayscale image quality and usually produce larger files. The default is `80`.

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

<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="compression_level" type="string">
  Ready-to-use compression profile: `low`, `medium`, `high`, or `aggressive`. When omitted, the standard configuration is applied. Presets also enable font subsetting and font stream compression.
</ParamField>

<ParamField body="color_quality" type="integer" default="80">
  JPEG2000 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 `compression_level`.
</ParamField>

<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 the selected `compression_level`, when supplied.
3. It applies `color_quality`, 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                      | Standard targets                                                                          | JPEG, quality 60                       | Unchanged           |
  | `compression_level`             | Targets from the selected preset                                                          | JPEG2000, quality 80 unless overridden | Subset and compress |
  | `color_quality` only            | Standard targets                                                                          | JPEG2000 at the selected quality       | Unchanged           |
  | `config` only                   | Start with the standard configuration, then replace only the values supplied in `config`. |                                        |                     |
  | Preset or quality plus `config` | Apply the preset and quality first, then replace only the values supplied in `config`.    |                                        |                     |

  <Tip>
    Specify the narrowest override you need. Every omitted value continues to come from the base configuration, or from the preset and `color_quality` 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 JPEG2000 quality 80 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. A `color_quality` of 80 maps to JPEG2000 `rates` with `quality_layers: [20]`.

  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": "jpeg2000",
            "compression_params": {
              "quality_mode": "rates",
              "quality_layers": [20]
            }
          }
        },
        "grayscale": {
          "skip": false,
          "downsample": {
            "skip": false,
            "downsample_ppi": 150,
            "threshold_ppi": 225
          },
          "compression": {
            "skip": false,
            "compression_format": "jpeg2000",
            "compression_params": {
              "quality_mode": "rates",
              "quality_layers": [20]
            }
          }
        },
        "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 `compression_level` 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 JPEG2000 compression.

  ```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}
  {
    "compression_level": "high",
    "color_quality": 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>
                  </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. This is also the configuration applied when the request contains only `url`.

  ```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. 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).

<ResponseField name="pageCount" type="integer">
  Number of pages in the output PDF.
</ResponseField>

<ResponseField name="error" type="boolean">
  `false` for a successful request.
</ResponseField>

<ResponseField name="status" type="integer">
  PDF.co status code. Success returns `200`. For more information, see [Response Codes](/api/response-codes).
</ResponseField>

<ResponseField name="credits" type="integer">
  Credits consumed by the request.
</ResponseField>

<ResponseField name="remainingCredits" type="integer">
  Credits remaining for the account.
</ResponseField>

<ResponseField name="duration" type="integer">
  Processing duration in milliseconds.
</ResponseField>

<ResponseField name="url" type="string">
  Temporary URL of the result. With size protection, it can point to a copy of the original PDF.
</ResponseField>

<ResponseField name="name" type="string">
  Output file name.
</ResponseField>

<ResponseField name="outputLinkValidTill" type="string">
  UTC timestamp when the temporary URL expires.
</ResponseField>

<ResponseField name="jobId" type="string">
  Present in the initial response when `async` is `true`.
</ResponseField>

<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>
    ```bash cURL (minimal) 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"
      }'
    ```

    ```bash cURL (preset) 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",
        "compression_level": "high",
        "color_quality": 80
      }'
    ```

    ```bash cURL (config override) 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",
        "compression_level": "high",
        "config": {
          "images": {
            "monochrome": {
              "compression": {
                "compression_format": "ccitt_g4"
              }
            }
          },
          "fonts": { "subset": false }
        }
      }'
    ```

    ```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",
          compression_level: "medium",
          color_quality: 80,
          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",
            "compression_level": "medium",
            "color_quality": 80,
            "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",
        ["compression_level"] = "medium",
        ["color_quality"] = 80,
        ["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\", \"compression_level\": \"medium\", \"color_quality\": 80, \"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",
        "compression_level" => "medium",
        "color_quality" => 80,
        "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>
