REST API v1

API Reference

Process images programmatically. Try 3 ops free without signing up, or create a free account for 25/month. Seller and Pro plans unlock more with an API key.

Overview

All API requests are made to the base URL:

https://api.mochify.app

Free account

25

ops/mo · 3 without signup

Seller tier

300

operations / month

Pro tier

1,200

operations / month

Growth tier

5,000

operations / month

Max file size

75 MB

paid plans · 20 MB free

Without an account: 3 ops/month (IP-based). Free account: 25 ops/month. Paid API keys use per-key limits. Each image returned counts as one operation, however many transformations were applied to it. A request for multiple formats or sizes of one file returns multiple images and costs one operation per output.

Authentication

The free tier requires no authentication. API keys are generated from your dashboard and passed as a Bearer token.

Header
Authorization: Bearer mchy_••••••••••••••••
!

API keys are shown once at creation and cannot be retrieved again. Store them securely. You can regenerate a key from your dashboard at any time.

POST

/v1/squish

Compress and/or convert a single image. Send the raw image bytes as the request body. Returns the processed image as a binary blob.

Query Parameters

Parameter Default Description
type
jpg

Output format.

One of: jpg · png · webp · avif · jxl

width
-

Target width in pixels. Aspect ratio is preserved unless smartCrop is also set. Omit or set to 0 for unconstrained. For SVG input, the vector is drawn at the requested width.

height
-

Target height in pixels. Omit or set to 0 for unconstrained.

quality
auto

Output quality (1–100). With smartCompress this is the quality of the subject: smart compression never lowers it. JXL maps linearly, so 70 is about distance 3.0. 100 is the best lossy setting, not lossless: see lossless.

smartCompress
false

Saliency-guided background softening. The subject is left untouched and encoded at quality; the background is lightly low-passed so it costs fewer bytes (roughly 15–40% smaller, depending on format and how much of the frame is background). The background does come out slightly softer. Skipped when there is no clear subject, the subject fills the frame, the background was removed, HDR is being carried, or lossless is set: see X-Mochify-SmartCompress. Accepts 1 or true.

smartCrop

alias: crop

false

Saliency-guided crop, centered on the detected subject. Requires both width and height. Accepts 1 or true.

removeBackground
false

AI background removal. Output is PNG, WebP or AVIF with an alpha channel; JPEG outputs flatten to white. Available on every plan, including Free. Accepts 1 or true.

background
white

Fill behind transparent areas when the output cannot keep them: every jpg output with transparency, such as an SVG. With removeBackground it also replaces the transparent background on png, webp, avif and jxl output. Otherwise those formats keep the alpha channel and ignore it.

One of: black · a six-digit hex color, with # written as %23 (%23F5F5F5) · transparent. Any other value, including color names and three-digit hex, fills white.

rotate
0

Clockwise rotation in degrees.

Supported values: 90 · 180 · 270

stripExif
true

Strip EXIF metadata from the output. Set to false or 0 to preserve metadata.

optimizeForWeb
false

Progressive encoding plus 4:2:0 chroma subsampling for the smallest browser-delivered file. Accepts 1 or true.

brightness
0

Exposure adjustment. Range -100 (darkest) to +100 (brightest). 0 is no change.

clarity
false

Midtone contrast enhancement: makes images look crisper without affecting overall exposure. Accepts 1 or true.

hdr
false

Ultra HDR / ISO 21496-1 gain map handling. 1 or true preserves a gain map the source already has and never invents one, so it is ignored if the source is not HDR. generate does that and synthesizes a gain map when the source is plain SDR.

Only jpg output can carry a gain map (jxl carries HDR by a different route; avif, webp and png cannot). Skipped when combined with brightness, clarity or removeBackground, since those change the base the gain map is a ratio to. Check X-Mochify-HDR for what was actually emitted.

lossless
false

Pixel-exact output. Accepts 1 or true, overrides quality, and disables smartCompress softening.

Only jxl, webp and png can honor it: jpg and avif are rejected with a 400 rather than silently encoded lossy. A source that is already lossy (JPEG, AVIF, HEIC) is re-encoded at the highest lossy setting instead, since nothing can restore what that file already discarded. Expect the output to be larger than the input: lossless preserves pixels, not file size. Check X-Mochify-Lossless for what was actually emitted.

targetBytes
-

Ceiling on the output file size, in bytes. An upper bound, never a size to pad up to: if the requested quality already fits, that encode is returned untouched.

No encoder can predict a compressed size without encoding, and no quality value maps to one: bytes at a fixed quality are a property of the pixels, spanning roughly 3× (JPEG) to 10× (AVIF) between a flat graphic and a detailed photo at identical dimensions. So this measures instead, typically 1–3 encodes, at most 5, extrapolating from each real measurement rather than bisecting. Minimum 1024. Cannot be combined with lossless (a 400): a pixel-exact file is whatever size it is. Applies per output, so a multi-variant request means every variant fits, not that they sum to it. Check X-Mochify-Target for whether the ceiling was actually reached.

targetLever
both

Which knob targetBytes may turn: quality (re-encode lower, never resize), dimensions (downscale, never change quality), or both (quality first, then downscale).

Requires targetBytes. quality is a 400 for png output, which has no quality control at all; use dimensions or both, where scaling is the only lever anyway. The dimensions lever is also withheld when an HDR gain map is being carried (hdr=1 on an Ultra HDR source), because resizing the base would invalidate the map: quality alone is used, and the target may report floor. When the lever moves, X-Mochify-Target-Scale reports how far.

Request Headers

Content-Type

MIME type of the uploaded image. For example image/jpeg, image/webp, image/avif, image/png, image/heic, image/jxl.

Response

Header / Field Description
Body

Raw compressed image bytes. Supported input formats: JPEG, PNG, WebP, AVIF, HEIF, JXL and SVG. Formats are detected from the file's content.

X-Latency-Ms

Processing time in milliseconds.

X-Mochify-Optimized

true if the output is smaller or the format changed; false if the original was returned unchanged.

X-Mochify-Reason

Present when X-Mochify-Optimized: false. Explains why the original was returned, for example "Original was smaller".

X-Mochify-Saliency

Subject coverage (0.000–1.000): the mean of the saliency mask. Only present when smartCompress=true.

X-Mochify-SmartCompress

Only present when smartCompress=true, and describes the returned bytes. background = the background was softened. skipped:no-subject, skipped:subject-fills-frame, skipped:background-removed, skipped:hdr or skipped:lossless = the pixels were left alone, and why. One value for a multi-variant ZIP.

X-Mochify-Target

Present only when targetBytes was set. hit = the returned bytes are at or under the ceiling. floor = the levers ran out first, and this is the smallest output that could honestly be produced: still a real, usable image, not an error. For a multi-variant ZIP, hit means every entry fits. Worth checking: a floor response is a 200, so it is otherwise indistinguishable from success.

X-Mochify-Target-Encodes

How many encodes the size search spent, including the first one the pipeline would have performed anyway. Summed across variants for a ZIP.

X-Mochify-Target-Scale

Extra downscale the dimensions lever applied, relative to the geometry that was requested (for example 0.710). Omitted when the lever did not move, i.e. the output is exactly the size asked for.

X-Mochify-Quality

The quality the returned bytes were encoded at. Present when smartCompress=true or when a targetBytes search ran, in which case it is the value the search settled on.

X-Mochify-BgRemoved

true when background removal was applied.

X-Mochify-HDR

Only present when hdr was requested. true means the emitted file carries headroom the source captured, generated means headroom Mochify synthesized, false means the output carries none. It describes the bytes actually returned, so a format that cannot hold a gain map reports false even when the source was HDR.

X-Mochify-Lossless

Only present when lossless was requested. true means the returned bytes reproduce the submitted pixels exactly; downgraded means they are the best lossy encode instead, because the source was already lossy or because HDR reconstruction replaced the submitted pixels.

Examples

cURL
curl -X POST "https://api.mochify.app/v1/squish?type=webp&stripExif=1" \
  -H "Content-Type: image/jpeg" \
  -H "Authorization: Bearer mchy_your_api_key" \
  --data-binary @photo.jpg \
  --output photo.webp
JavaScript
const file = document.querySelector('input[type="file"]').files[0];

const response = await fetch(
  'https://api.mochify.app/v1/squish?type=avif&stripExif=1',
  {
    method: 'POST',
    headers: {
      'Content-Type': file.type,
      'Authorization': 'Bearer mchy_your_api_key',
    },
    body: file,
  }
);

const blob = await response.blob();
const latency = response.headers.get('X-Latency-Ms');

// Save the file
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.avif';
a.click();
Python
import requests

with open('photo.jpg', 'rb') as f:
    response = requests.post(
        'https://api.mochify.app/v1/squish',
        params={'type': 'webp', 'stripExif': '1'},
        headers={
            'Content-Type': 'image/jpeg',
            'Authorization': 'Bearer mchy_your_api_key',
        },
        data=f,
    )

with open('output.webp', 'wb') as out:
    out.write(response.content)

print(f"Done in {response.headers.get('X-Latency-Ms')}ms")
POST

/v1/pdf

The PDF toolkit. A single endpoint with five operations, selected with ?op=. Four of them take a PDF as the raw request body; op=create takes images and produces one.

Operations

op Returns Description
optimize PDF

Recompress the images inside a PDF, in place, and return a smaller PDF. Text, fonts, vector art, links and layout are untouched, so the document stays searchable.

extract ZIP

Pull the images embedded in a PDF out of it, at the resolution they were stored at. These are the pictures somebody placed into the document, not a render of each page.

rasterize ZIP

Render every page to an image, text and all. The default operation when op is omitted.

split ZIP

Explode a PDF into one single-page PDF per page. Takes no parameters.

create PDF

Build a PDF from images, one page per image in upload order. Send a single raw image body, or a multipart/form-data upload with each file appended as images.

Images inside a PDF are always JPEG. The PDF format has no WebP, AVIF or JXL, so op=optimize re-encodes with jpegli and nothing else is offered. Those formats are available from op=extract, which writes files out rather than back into the document.

Parameters: op=optimize

Parameter Default Description
quality 75

jpegli quality for the re-encode (1–100). Lower is smaller.

maxDpi 150

Target resolution, measured against how large each image is actually drawn on the page rather than how many pixels it stores. Use 96 for screen and email, 150 for general use, 300 to keep print quality. Set 0 to disable and rely on maxDimension alone.

A 4000px photo placed in a two-inch box is 2000 DPI of waste; the same photo full-bleed on A4 is not. Only the drawn size can tell those apart, so it is read from the content stream. An image drawn at several sizes is targeted at its largest, never its average.

maxDimension 2000

Absolute backstop applied alongside maxDpi: no image keeps a side longer than this. It is what limits an image the placement scan never saw drawn. Set 0 to remove the cap.

Resizing is layout-safe: a PDF image is drawn into the unit square, so changing its pixel size cannot move or rescale it on the page.

minSize 64

Leave images smaller than this (on either axis) untouched. Keeps spacers, rules and bullet glyphs out of the work.

It never returns a larger file. If recompression would not help, the original bytes come back and X-Mochify-Saved-Pct is 0. Running it twice changes nothing.

It never drops what it cannot handle. Images in encodings outside the supported set (CMYK, indexed palettes, JPEG 2000, CCITT and JBIG2 scans, stencil masks) are passed through byte-for-byte rather than skipped.

Parameters: op=extract

Parameter Default Description
type original

Output format for the extracted files.

One of: original · png · jpg · webp · avif · jxl

original copies each embedded JPEG out byte-for-byte, with no re-encode and no quality loss; images stored in other encodings come out as lossless PNG. Setting maxWidth forces a re-encode.

quality 82

Output quality (1–100). Ignored for original and png.

maxWidth 0

Cap the width of each extracted image, preserving aspect ratio. 0 leaves sizes alone.

minSize 64

Skip images smaller than this on either axis. Exported PDFs are full of 1×1 spacers and hairline rules; without a floor the archive is mostly junk. Set 0 to take everything.

An image repeated across pages, such as a logo or letterhead, is returned once rather than once per page. Files are named page-003-image-002.webp, recording the page each image was first found on.

Parameters: op=rasterize

Parameter Default Description
type png

Output image format for each rendered page.

One of: png · jpg · webp · avif · jxl

dpi 150

Render resolution, 36–300. Use 72 for screen previews, 300 for print.

quality 82

Output quality (1–100). Ignored for png.

Parameters: op=create

Parameter Default Description
page fit

Page size. fit makes each page exactly the image, with no whitespace.

One of: fit · a4 · letter

quality 82

jpegli quality (1–100) for the JPEG embedded in each page.

dpi 96

Pixels per inch used to size a fit page, 36–600.

maxWidth 0

Downscale images wider than this before embedding. 0 leaves them alone.

combine 1

Set to 0 to get one single-page PDF per image, returned as a ZIP, instead of one combined document.

Saving to your own storage

Parameter Default Description
dest -

Write the result to your connected storage instead of returning it. One of bucket or drive. The response becomes a JSON receipt, { "stored": true, ... }, and the bytes go straight from Mochify to your storage rather than back through the browser.

Only the operations that produce a single file accept it: op=optimize, and op=create with combine=1. extract, rasterize, split and combine=0 return an archive and are refused with a 400 rather than filed under a made-up name.

name -

Required whenever dest is set. The destination filename, including extension. Without it every file in a batch would derive the same fallback name and overwrite each other.

Requires a signed-in account with that destination connected in the dashboard; an anonymous request gets a 401. If the write fails, the file is returned as a normal download with an X-Mochify-Bucket-Error header rather than erroring, so a storage problem never costs you the work.

Plans and limits

Plan access

optimize, extract, rasterize and split require a paid plan (Seller, Pro, Growth, or Day Pass). create is available on every plan, including Free.

Request body

100 MB maximum.

Pages

200 absolute. For the PDF-in operations, Growth is unlimited within that and other paid plans cap at 10 pages.

Images per create

Free 3, Seller / Pro / Day Pass 20, Growth 200.

Cost

One operation per request, whatever the page or image count.

Response Headers

Header Description
X-Latency-Ms

Processing time in milliseconds. All operations.

X-Mochify-Pages

Pages processed. rasterize, split, create.

X-Mochify-Images

Images returned in the archive. extract.

X-Mochify-Skipped

Images skipped for being too small, too large, or in an encoding that cannot be extracted. extract.

X-Mochify-Duplicates

Repeat references to an image already returned once. extract.

X-Mochify-Images-Recompressed

Images actually replaced. optimize.

X-Mochify-Images-Kept

Images left as they were, either because recompressing them would not have saved enough or because they were outside the supported set. optimize.

X-Mochify-Original-Bytes

Size of the submitted PDF. optimize.

X-Mochify-Saved-Pct

Percentage saved, as a whole number. 0 means the original was returned unchanged. optimize.

Examples

cURL · compress a PDF
curl -X POST "https://api.mochify.app/v1/pdf?op=optimize&quality=75&maxDpi=150" \
  -H "Content-Type: application/pdf" \
  -H "Authorization: Bearer mchy_your_api_key" \
  --data-binary @report.pdf \
  --output report-compressed.pdf \
  --dump-header -
# X-Mochify-Images-Recompressed: 12
# X-Mochify-Saved-Pct: 47
cURL · extract the images as WebP
curl -X POST "https://api.mochify.app/v1/pdf?op=extract&type=webp&maxWidth=1600" \
  -H "Content-Type: application/pdf" \
  -H "Authorization: Bearer mchy_your_api_key" \
  --data-binary @brochure.pdf \
  --output images.zip
# Omit type= to get the embedded images byte-for-byte, with no re-encode.
JavaScript · compress and report the saving
const file = document.querySelector('input[type="file"]').files[0];
const response = await fetch(
  'https://api.mochify.app/v1/pdf?op=optimize&quality=75',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/pdf',
      'Authorization': 'Bearer mchy_your_api_key',
    },
    body: file,
  }
);
const blob = await response.blob();
const saved = response.headers.get('X-Mochify-Saved-Pct');
// 0 means the PDF was already well compressed and came back untouched.
console.log(saved === '0' ? 'Already optimal' : `Saved ${saved}%`);
Python · render pages to PNG
import requests, zipfile, io
with open('report.pdf', 'rb') as f:
    response = requests.post(
        'https://api.mochify.app/v1/pdf',
        params={'op': 'rasterize', 'type': 'png', 'dpi': '300'},
        headers={
            'Content-Type': 'application/pdf',
            'Authorization': 'Bearer mchy_your_api_key',
        },
        data=f,
    )
zipfile.ZipFile(io.BytesIO(response.content)).extractall('pages/')
print(f"{response.headers.get('X-Mochify-Pages')} pages rendered")
cURL · build a PDF from images
curl -X POST "https://api.mochify.app/v1/pdf?op=create&page=a4" \
  -H "Authorization: Bearer mchy_your_api_key" \
  -F "images=@page1.jpg" \
  -F "images=@page2.jpg" \
  -F "images=@page3.jpg" \
  --output album.pdf
GET

/v1/checkTokens

Returns the number of remaining operations for the current IP or API key. Useful for checking quota before submitting a batch.

Response

Field Type Description
remaining number

Total remaining operations for the current IP or API key.

available boolean

true if at least one operation remains.

cURL
curl "https://api.mochify.app/v1/checkTokens" \
  -H "Authorization: Bearer mchy_your_api_key"
# Response
# { "remaining": 983, "available": true }

Errors

The API uses standard HTTP status codes.

Status Meaning
200

Success. Response body is the processed image.

400

Bad request: missing or invalid parameters.

401

Unauthorized: invalid or missing API key.

403

Your plan does not include this operation. The PDF-in operations on /v1/pdf require a paid plan. Upgrade to enable them.

413

Payload too large. The request body, or the output it would produce, exceeds the limit for your plan.

415

Unsupported media type. The body was not a file this operation accepts, for example an image sent to a PDF-in operation.

422

The file was readable but could not be processed as asked. The body explains why, for example a PDF with more pages than your plan allows, a password-protected PDF, or a PDF holding no extractable images.

429

Rate limit exceeded. Free tier resets monthly; paid tiers reset on your billing date. Upgrade for higher limits.

500

Server error: the file could not be processed.

503

At capacity. Retry after the delay in the Retry-After header. Nothing is charged for a request that returns this.

Need more operations?

Upgrade to Pro for 1,200 operations/month and a dedicated API key.