Tinify.dev processes images server-side and reports honest results: compression
never returns a file larger than the input (you get the original plus
optimized: false instead), while resize and conversion report size changes
plainly because they can legitimately grow a file.
Limits that shape your plan: 40 MB and 50 megapixels per image, results expire after two hours, supported formats are AVIF/WebP/JPEG/PNG (GIF in the web tools and anonymous endpoints). Free API tier: 500 successful operations per month; failed calls are not billed.
Pick the right path
- Tinify MCP tools available (tool names like
compress_image,resize_image,crop_image,convert_image,get_usagefrom an MCP server namedtinify): use them. They validate paths, refuse to overwrite the source unless asked, write<name>.min.<ext>next to the input, and return byte counts. Checkget_usagefirst when running a large batch so you do not burn through the monthly quota mid-job. - No MCP but
TINIFY_API_KEYis set (key formattnf_live_...ortnf_test_...): call the REST API directly (below) or use the CLI:npx -y @tinify-dev/client compress ./img.png --out ./out. - Neither: for one-off files point the user at https://tinify.dev (free web tools, no account); for automation tell them keys are free at https://tinify.dev/developers - do not invent a key or skip authentication.
REST API essentials
Base: https://api.tinify.dev/api/v1 - Bearer auth - Idempotency-Key
header required (any UUID; reuse it when retrying the same logical request).
curl -X POST https://api.tinify.dev/api/v1/images/compress \
-H "Authorization: Bearer $TINIFY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@photo.jpg" \
-F "quality_mode=balanced" # balanced | best_quality | lossless
Other operations, same shape:
/images/resize-width,height, orscale;keep_aspect_ratio=true/images/crop-x,y,width,height(pixels)/images/convert-format=avif|webp|jpeg|png(rejects same-format with 422)- compress also accepts
target_size_bytes(>= 10240, < input size; checktarget_size_achievedin the response -falsemeans best effort) GET /usage- plan, used, remaining
Responses wrap payload in data with a top-level request_id. Download the
result promptly from data.download_url (relative to the API origin, expires
in two hours). Errors use a stable envelope: {"error": {"code", "message"}, "request_id"} - retry only on 429/503, honoring Retry-After.
Working rules
- Never silently overwrite the user's source file; write alongside it unless they explicitly ask for in-place replacement.
- Report per-file before/after bytes and the total saved. If
optimizedisfalse, say so - do not present the original as a win. - Lossless mode is not available for JPEG, and cannot combine with
target_size_bytes. - For batches, process a few files at a time rather than all at once, and fail the batch loudly on auth/quota errors instead of skipping files quietly.
- Conversion to a "smaller" format can produce a bigger file (e.g. small PNG icons to WebP). Compare bytes and keep whichever the user actually asked for, stating the trade-off.