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

# Upload compression

> Upload documents faster with gzip or zstd on /convert/raw.

# Upload compression

`POST /convert/raw` accepts the compressed body. You reduce network bytes and speed up large uploads. Indicates the algorithm with the header `content-encoding`.

| `content-encoding` | Algorithm                                                           |
| ------------------ | ------------------------------------------------------------------- |
| `gzip`             | Compatible everywhere.                                              |
| `zstd`             | Better ratio and fast decompression. Recommended for large uploads. |

<Note>
  Only `/convert/raw` supports `content-encoding`. `/convert` (multipart) and `/convert/from-url` no.
</Note>

\##gzip

```bash theme={null}
gzip -c report.pdf > report.pdf.gz

curl -X POST "https://api.markpdf.tech/convert/raw?filename=report.pdf" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/pdf" \
  -H "content-encoding: gzip" \
  --data-binary "@report.pdf.gz"
```

## zstd

```bash theme={null}
zstd -T0 -3 report.pdf -o report.pdf.zst

curl -X POST "https://api.markpdf.tech/convert/raw?filename=report.pdf" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/pdf" \
  -H "content-encoding: zstd" \
  --data-binary "@report.pdf.zst"
```

## From code

<CodeGroup>
  ```python Python (requests) theme={null}
  import gzip
  import requests

  with open("report.pdf", "rb") as f:
      body = gzip.compress(f.read())

  resp = requests.post(
      "https://api.markpdf.tech/convert/raw?filename=report.pdf",
      headers={
          "x-api-key": "YOUR_API_KEY",
          "content-type": "application/pdf",
          "content-encoding": "gzip",
      },
      data=body,
  )
  print(resp.headers.get("x-wire-bytes"), resp.headers.get("x-input-bytes"))
  ```

  ```ts Node.js (fetch) theme={null}
  import { readFileSync } from "node:fs";
  import { gzipSync } from "node:zlib";

  const raw = readFileSync("report.pdf");
  const body = gzipSync(raw);

  const resp = await fetch(
    "https://api.markpdf.tech/convert/raw?filename=report.pdf",
    {
      method: "POST",
      headers: {
        "x-api-key": "YOUR_API_KEY",
        "content-type": "application/pdf",
        "content-encoding": "gzip",
      },
      body,
    },
  );
  console.log(resp.headers.get("x-wire-bytes"), resp.headers.get("x-input-bytes"));
  ```
</CodeGroup>

## How much do you save in practice

PDFs already usually have compressed internal streams (JPEG images, text
with Flate), so gzip/zstd over a typical PDF saves less than over text
flat. Even so, in documents with a lot of uncompressed text (PDFs generated
from Word/LaTeX without optimization) or in Markdown output, the difference is
remarkable:

| Content                             | Original size |           gzip |         zstd -3 |        zstd -19 |
| ----------------------------------- | ------------: | -------------: | --------------: | --------------: |
| PDF with already compressed images  |          10MB |  \~9.4 MB (6%) |   \~9.3 MB (7%) |   \~9.1 MB (9%) |
| PDF text-heavy (contracts, reports) |          10MB | \~4.2 MB (58%) |  \~4.0 MB (60%) |  \~3.3 MB (67%) |
| Markdown output (`output_url`)      |           2MB | \~0.6 MB (70%) | \~0.44 MB (78%) | \~0.35 MB (82%) |

For uploads (`/convert/raw`), use a low level (`gzip -1` or `zstd -3`):
the bottleneck is the network, not the CPU, and a high level takes longer to
compressing what you save on transfer. For `output_url` (where
you compress once on the server and your backend decompresses it N times),
a higher level may compensate.

## Recommendations

* For large uploads, `zstd -3` usually gives the best ratio/CPU balance.
* Avoid Brotli: saves bytes but consumes much more CPU.
* The `X-Input-Compressed-Bytes` header tells you how many bytes arrived over the network after compression; `X-Input-Bytes` is the actual uncompressed size.

<Warning>
  A `content-encoding` other than `gzip` or `zstd` responds `415`.
</Warning>

## Output compression (`output_encoding`)

When you use `output_url` in `/convert/from-url`, the converted Markdown is uploaded compressed to your storage. The `output_encoding` parameter controls the algorithm:

| `output_encoding` | Ratio  | CPU    | Recommended use                                                     |
| ----------------- | ------ | ------ | ------------------------------------------------------------------- |
| `identity`        | 1.0×   | 0      | Very small PDFs                                                     |
| `gzip`            | \~0.30 | medium | Universal compatibility (old browsers, CDNs)                        |
| `zstd`            | \~0.22 | low    | **Recommended for BYOS server-to-server**. Streaming decompression. |

**When there is `output_url`, the API auto-promotes to `zstd` if you do not specify another value.** Reason: in the BYOS case there are no browsers or CDNs in the way; client and server are backends that can unpack native zstd. `zstd` yields about 30% fewer bytes than gzip at similar CPU cost and supports streaming decompression on the client.

Do not want to depend on S3/R2 for `output_url`/`url`? See [Self-hosted S3](/docs/concepts/self-hosted-storage) for open source alternatives that you can self-host.

Overwriting is trivial:

```json theme={null}
{
  "url": "...",
  "output_url": "...",
  "output_encoding": "gzip"
}
```

## Raw limit measures uncompressed bytes

The raw upload limit (`RAW_UPLOAD_MAX_BYTES`, 12 MB by default) applies to the **uncompressed** size, not the bytes traveling over the network. Compression does not let you upload a larger PDF through `/convert/raw`: a `gzip`/`zstd` body that decompresses above 12 MB is rejected with `413`. Compression only saves network and upload time.

For large PDFs, upload them to your storage and use [`/convert/from-url`](/docs/api/convert-from-url), which does not apply this limit. See [Limits](/docs/concepts/limits) and [Raw vs storage URL](/docs/performance/raw-vs-from-url).

## Troubleshooting

| Symptom                                                                                   | Cause                                                                                                                     | Solution                                                                                                                                    |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `415 Unsupported Media Type`                                                              | `content-encoding` other than `gzip`/`zstd` (for example `br` or `deflate`)                                               | Use only `gzip` or `zstd`. Brotli and deflate are not supported in `/convert/raw`.                                                          |
| `413` with a file that "should" fit                                                       | The limit is measured **decompressed**: you compressed a 15 MB PDF to 3 MB, but it is still a 15 MB PDF once decompressed | Upload to your storage and use `/convert/from-url`, or request a higher `RAW_UPLOAD_MAX_BYTES` if your deploy allows it.                    |
| Server takes longer than uncompressed                                                     | Very high compression level (`zstd -19`, `gzip -9`) on a large file                                                       | Lower the level (`zstd -3`, `gzip -1`) for upload; strong CPU compression does not pay off on fast networks.                                |
| `X-Input-Compressed-Bytes` and `X-Input-Bytes` are the same despite sending `gzip`/`zstd` | The server did not decompress because the header did not arrive or an intermediate proxy removed it                       | Verify that the proxy/CDN in front of the API does not normalize or remove `content-encoding` before forwarding.                            |
| Compression doesn't help at all                                                           | The PDF already comes with compressed internal streams (JPEG/PNG images, embedded sources)                                | It's to be expected: an image-heavy PDF is already close to its minimum size. Compression helps most on text-heavy PDFs or Markdown output. |

<Tip>
  If you don't know which algorithm has the best ratio for your specific document, try
  both with `gzip -c file.pdf | wc -c` and `zstd -3 -c file.pdf | wc -c`
  before you decide — the difference depends a lot on the internal content of the PDF.
</Tip>
