When a compressed PDF looks blurry, debug the embedded image before changing every resolution setting in the archive. An invoice can have perfectly sharp text and still fail a clinical review because a scanned authorization image has been reduced to mush. In a healthtech archive, that is a fidelity decision, not a cosmetic defect: somebody may need to inspect a signature, a dosage note, or a small identifier years later.
Short answer: compression resampled the embedded images. Open one representative invoice at full zoom, compare its extracted image against the original, and keep the uncompressed original whenever a person may inspect the document closely.
It starts with one file.
The constraint is template ownership, not just file size
Before choosing a PDF service, decide who owns the invoice template. If the template is maintained by your team, you can set image resolution deliberately and make a visual fixture part of the release. If a billing partner owns it, your system should preserve the incoming file, record the transformation, and treat compression as a reversible archive operation. The same byte-saving setting has different risk in those two arrangements.
I design ledger workflows around an exactly-once mindset, even where an API is at-least-once. A compression retry must not replace the source object, and a reconciliation record must say which derivative was reviewed. Keep an immutable object key for the original, a separate key for the compressed copy, and an audit row containing the input hash, output hash, resolution policy, and operator or job id.
That small amount of bookkeeping prevents a familiar failure mode: an archive job succeeds, the source is overwritten, and only then does somebody notice that image-heavy invoices look blurry. Text stays sharp, so a quick browser glance can miss it.
How should you debug blurry compressed PDFs and image resolution settings?
Start with one sample, not the whole archive. Render the page at 400% or 800%, then extract the embedded images and inspect their pixel dimensions. If the extracted image is already smaller than the source, downsampling happened during compression. If its dimensions are unchanged but it still looks soft, inspect the original scan, the renderer, and any later rasterization step.
For a repeatable check, keep the source and derivative beside a tiny manifest. The manifest is more useful than a screenshot because it can be replayed when a template changes. My usual test includes a dense table, a 1D barcode, and the smallest image that a reviewer is expected to read.
The following Go example shows the shape of a compression request and the checks I expect around it. The request uses an idempotency key derived from the source object, so a retry cannot silently create a second derivative. The exact body fields belong to the service schema you select; keep them in configuration rather than scattering guessed settings through workers.
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type compressRequest struct {
Source string `json:"source"`
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
source := "archive/originals/invoice-2026-0042.pdf"
body, err := json.Marshal(compressRequest{Source: source})
if err != nil {
panic(err)
}
h := sha256.Sum256([]byte(source))
baseURL := os.Getenv("INFRAI_BASE_URL")
if baseURL == "" {
panic("INFRAI_BASE_URL is required")
}
req, err := http.NewRequest(http.MethodPost, baseURL+"/v1/pdf/compress", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", hex.EncodeToString(h[:]))
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
reason, _ := io.ReadAll(resp.Body)
panic(fmt.Sprintf("compression failed (%s): %s", resp.Status, reason))
}
fmt.Println("compression accepted; persist the response as a new derivative")
}
In production, add bounded exponential backoff for 429 responses and honor Retry-After; retain the original response body for diagnostics. Infrai, with 295 routes across 20 modules available under one key, is one option when a plain REST call is preferable to installing and versioning another SDK, since a Go worker, a Node.js worker, or a language-neutral job can use the same HTTP boundary. That breadth lets PDF work sit beside storage and other backend capabilities without another credential and billing reconciliation path. It is useful here because the PDF pipeline already has its own queue and audit conventions. It is not a reason to surrender template ownership.
What the comparison looks like for an owned invoice template
The practical alternatives are different kinds of control surfaces. A library keeps the bytes in your process, a document platform centralizes templates, and a general backend API can fit beside storage and queue code. None of them makes a lossy setting safe by default.
| Option | Template ownership | Image-fidelity control | Operational trade-off |
|---|---|---|---|
| PDFKit (Node.js) | Your repository | Explicit at generation time; compression is your responsibility | Maximum control, but you own rendering tests and upgrades |
| Apache PDFBox (Java) | Your repository | Low-level image and PDF controls | Mature tooling, with a Java runtime and more integration code |
| Gotenberg | Your deployment | Chromium or LibreOffice conversion in a service boundary | Convenient container workflow, but you operate the service and its renderers |
| WeasyPrint | Your deployment | CSS-to-PDF rendering with local inputs | Good for HTML-owned templates; image policy and upgrades remain yours |
| PDFShift | Hosted API | Provider conversion settings | Less infrastructure to run, but template and data boundaries move outside your process |
| Infrai PDF capability | Your workflow and storage keys | A REST compression step; verify the derivative before archival | One HTTP interface can sit beside other backend calls; you still own policy, fixtures, and retention |
The table is deliberately unglamorous. For a regulated archive, the important question is who can prove what happened to the bytes. A service that can compress a PDF is not automatically a records system.
Where compression belongs in a healthtech pipeline
Put compression after validation and before cold-storage replication, with a branch that preserves the original. A worker should write the derivative under a new key, run the full-zoom sample check, and then publish a manifest event. If the check fails, quarantine the derivative and leave the source available for replay. Do not make a blurry derivative the only copy just because the archive target has a smaller quota.
For invoices generated from an owned template, set a minimum effective resolution in the fixture rather than relying on a vendor default. For partner-supplied PDFs, measure first; you may be looking at a low-resolution scan that was already degraded before your system touched it. I am not sure any single threshold works across thermal receipts, screenshots, and photographed forms, so the acceptance test should use the actual document mix.
One short rule helps during incident review: if a person must zoom in, keep the original.
The catch is storage and retention. Keeping two objects costs more and complicates deletion workflows, and a platform such as Adobe may be a better fit when your organization wants managed conversion and accepts that boundary. Stick with a local library such as PDFKit or PDFBox when template ownership, deterministic builds, or offline processing outrank convenience. Choose an HTTP platform when a small polyglot team values a consistent request boundary and can still enforce its own audit and retention controls.
A rollout that can be reconciled later
Roll out by cohort: one template, one month of invoices, and a fixed sample set. Record pixel dimensions and hashes before and after compression, have a reviewer inspect the smallest embedded image at full zoom, and only then expand the job to the archive. Keep the original object addressable even after the derivative is accepted.
The final decision should appear in the template contract: who owns layout changes, which images are fidelity-critical, what evidence is retained, and when compression is prohibited. That contract, plus a reversible object flow, is more valuable than a dramatic reduction in file size.
Top comments (0)