Overview
All API requests are made to the base URL:
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.
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.
/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
type 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 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 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
Saliency-guided crop, centered on the detected subject. Requires both width and height. Accepts 1 or true.
region Crop to a rectangle you choose, as four comma-separated fractions of the image: x,y,w,h from the top-left corner (0.25,0.5,0.5,0.5 is the middle of the bottom half). Built for a caller whose own vision model picks the box, as in "crop to the feet": Mochify receives four numbers, never the image description.
Measured on the image as it displays upright, after EXIF orientation, and applied before everything else: width and height then size the region, smartCrop searches inside it, and rotate turns the result. A box running past an edge is trimmed to the image. Any value above 1 is a 400, because that is a pixel coordinate and would otherwise crop the wrong corner without complaint. Check X-Mochify-Region for the pixel rectangle actually cut.
redact Hide areas of the image. A ;-separated list of boxes in the same x,y,w,h fraction convention as region (a number plate, an address label, a screen), and/or faces to find and hide faces automatically. For example faces;0.5,0.64,0.18,0.06.
Boxes are fractions of the whole upright image even when region is also set: redaction runs first, so a crop can never uncover a hidden area. faces uses Mochify's own face detector on our servers, never a third-party service, and is best-effort: it can miss small faces in group photos and strong side profiles, so add boxes for any it misses. Detected faces are hidden with a margin for hair, ears and jawline. Up to 64 areas. Because hidden content can survive outside the pixels, a redacted image always has its metadata stripped, even with stripExif=false (EXIF can hold an unredacted thumbnail), and never carries an HDR gain map. Check X-Mochify-Redacted and X-Mochify-Redacted-Faces for what was hidden.
redactStyle How hidden areas look: fill (solid black) or blur. Requires redact.
Use fill for anything that must stay secret. blur is a cosmetic look: blurred text and faces can sometimes be partly recovered.
erase Remove objects and fill the area with plausible background (inpainting): a ;-separated list of up to 8 boxes in the region convention, e.g. 0.62,0.55,0.12,0.2. Draw each box around the whole object with a margin, shadow included; any part left outside the box can be regrown. Paid plans.
Mochify finds each object's outline inside its box and repaints only the outline (plus a margin), so less of the scene is invented; when no clear outline is found, the box itself is repainted. Best on small distractions against texture or plain backgrounds (a bin, wires, a stranger); large removals come out softer, because the inpainting model paints at a fixed 512 by 512. Erase invents content, so never use it to hide anything sensitive: that is redact. Applied before redact and region. Metadata is always stripped and HDR dropped (the EXIF thumbnail and a gain map would still show the object). Check X-Mochify-Erased, X-Mochify-Erase-Outlined and X-Mochify-Erase-Coverage.
recolor Change an object's color and keep its shading: a ;-separated list of up to 8 x,y,w,h:#rrggbb entries (box in the region convention, target color as hex), e.g. 0.4,0.5,0.3,0.2:#2563eb. Paid plans.
Mochify finds the object's outline inside each box and changes only the object, never the rectangle: its hue becomes the target's, and its lightness and saturation shift to the target's while every fold, highlight and texture keeps its place. Not generative. Box exactly the part to recolor (the jacket, not the whole person): everything inside the outline takes the color. A box with no clear object is skipped, not recolored. Applied after erase, before redact and region. Metadata is always stripped and HDR dropped. Check X-Mochify-Recolored and X-Mochify-Recolor-Skipped.
removeBackground 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 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 Clockwise rotation in degrees.
Supported values: 90 · 180 · 270
stripExif Strip EXIF metadata from the output. Set to false or 0 to preserve metadata.
optimizeForWeb Progressive encoding plus 4:2:0 chroma subsampling for the smallest browser-delivered file. Accepts 1 or true.
brightness Exposure adjustment. Range -100 (darkest) to +100 (brightest). 0 is no change.
clarity Midtone contrast enhancement: makes images look crisper without affecting overall exposure. Accepts 1 or true.
hdr 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 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.
quantize Lossy PNG. Accepts 1 or true. Reduces the image to a dithered palette of up to 256 colors and emits an indexed PNG, typically 4–5× smaller than the lossless encode.
Without it a png is always pixel-exact, and quality alone never makes it lossy. With it, quality picks the palette size: omitted means the full 256 colors; 80 ≈ 108, 65 ≈ 56, 20 = 8. Applies to png outputs only: a request with no png output is a 400, and in a multi-variant request only the png entries are quantized. Cannot be combined with lossless (a 400). Alpha is preserved. Check X-Mochify-Quantize 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 Which knob targetBytes may turn: quality (re-encode lower, never resize), dimensions (downscale, never change quality), or both (quality first, then downscale).
Requires targetBytes. For png the quality lever is palette quantization: the pixel-exact encode is tried first, and the file is quantized only if that misses the ceiling (see quantize); dimensions keeps a png pixel-exact. 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
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.
X-Mochify-Quantize Present for a png output when quantize or targetBytes was requested. true means the returned PNG is a reduced palette (lossy); false means it is pixel-exact. With only targetBytes, false means the lossless encode already fit. For a multi-variant ZIP, true if any png entry was quantized.
X-Mochify-Region Only present when region was set. The pixel rectangle actually cut, as x,y,w,h in the upright source. Fractions round and an over-the-edge box is trimmed, so do not assume it equals the box you sent.
X-Mochify-Redacted Only present when redact was set. How many areas were hidden: your boxes plus any faces found.
X-Mochify-Redacted-Faces Only present when redact includes faces. How many faces the detector found. 0 is a successful response that hid no faces, so check it rather than assuming.
X-Mochify-Erased Only present when erase was set. How many boxes were painted out.
X-Mochify-Erase-Outlined Only present when erase was set. Of those, how many were repainted along the object's outline rather than the whole box. 0 means every box was repainted whole.
X-Mochify-Erase-Coverage Only present when erase was set. Approximate share of the frame repainted, 0 to 1. Larger removals come out softer.
X-Mochify-Recolored Only present when recolor was set. How many boxes had their object recolored.
X-Mochify-Recolor-Skipped Only present when recolor was set. How many boxes were left unchanged because no clear object was found in them. Redraw those boxes tighter.
Examples
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# Hide every face the detector finds, plus one box you chose
# (a number plate, as fractions x,y,w,h of the upright image),
# then crop to a region. Redaction runs first, so the crop
# cannot uncover anything. Output: WebP, 1600 wide.
curl -X POST "https://api.mochify.app/v1/squish?type=webp&width=1600&redact=faces;0.155,0.54,0.205,0.075&redactStyle=fill®ion=0.02,0.35,0.74,0.46" \
-H "Content-Type: image/jpeg" \
-H "Authorization: Bearer mchy_your_api_key" \
--data-binary @listing.jpg \
--output listing-redacted.webp \
--dump-header headers.txt
# headers.txt will include, for this call:
# X-Mochify-Redacted: 3 (2 faces found + 1 box, for example)
# X-Mochify-Redacted-Faces: 2 (0 is a success that hid no faces - check it)
# X-Mochify-Region: 11,224,423,294# Remove a bin, turn the car blue, output WebP. Boxes are fractions
# x,y,w,h of the upright image; recolor adds :#rrggbb. Paid plans.
curl -X POST "https://api.mochify.app/v1/squish?type=webp&erase=0.62,0.55,0.12,0.2&recolor=0.15,0.45,0.4,0.3:#2563eb" \
-H "Content-Type: image/jpeg" \
-H "Authorization: Bearer mchy_your_api_key" \
--data-binary @street.jpg \
--output street-edited.webp \
--dump-header headers.txt
# headers.txt will include, for example:
# X-Mochify-Erased: 1
# X-Mochify-Erase-Outlined: 1 (repainted along the bin's outline)
# X-Mochify-Erase-Coverage: 0.031 (share of the frame repainted)
# X-Mochify-Recolored: 1
# X-Mochify-Recolor-Skipped: 0 (non-zero: redraw that box tighter)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();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")import requests
# Faces found automatically, one box chosen by your own vision model,
# as fractions x,y,w,h of the upright image. Fill is the default style.
with open('listing.jpg', 'rb') as f:
response = requests.post(
'https://api.mochify.app/v1/squish',
params={'type': 'webp', 'width': 1600,
'redact': 'faces;0.155,0.54,0.205,0.075',
'region': '0.02,0.35,0.74,0.46'},
headers={'Content-Type': 'image/jpeg',
'Authorization': 'Bearer mchy_your_api_key'},
data=f,
)
faces = int(response.headers.get('X-Mochify-Redacted-Faces', '0'))
if faces == 0:
print('No faces found: check this one by hand') # 0 is a success, not an error
with open('listing-redacted.webp', 'wb') as out:
out.write(response.content)/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
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
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
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
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
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
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
optimize, extract, rasterize and split require a paid plan (Seller, Pro, Growth, or Day Pass). create is available on every plan, including Free.
100 MB maximum.
200 absolute. For the PDF-in operations, Growth is unlimited within that and other paid plans cap at 10 pages.
create Free 3, Seller / Pro / Day Pass 20, Growth 200.
One operation per request, whatever the page or image count.
Response Headers
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 -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: 47curl -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.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}%`);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 -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/v1/checkTokens
Returns the number of remaining operations for the current IP or API key. Useful for checking quota before submitting a batch.
Response
remaining number Total remaining operations for the current IP or API key.
available boolean true if at least one operation remains.
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.
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.