Skip to content
Image KB Reducer
Esc
↑↓navigate↵open⌘Jpreview
On this page

Errors

Every status and error code this API can return, and what each one means.

Every refusal from this API is a specific error code, never a bare non-2xx status with nothing to branch on. The response body is always { "error": "<code>", "message": "<human text>" }, with target-unreachable carrying three extra fields — see below.

This table is generated from — and kept in sync with, by a CI test — the ERROR_TABLE map and the router’s own responses in this API’s source (apps/api/src/errors.ts, apps/api/src/compress.ts, apps/api/src/form.ts, apps/api/src/auth.ts, apps/api/src/index.ts). If this page and that source ever disagree, the source is right and this page has a bug.

Status Code Meaning
400 bad-request a malformed request: target missing or not a parseable size, format not one of keep/jpeg/png/webp, allow_downscale not true/false, file field missing or not a file, or a multipart body that couldn’t be parsed at all
400 empty-file the uploaded file is a real file field, but it’s 0 bytes
401 unauthorized the Authorization header is missing, malformed, or doesn’t match a known key — these three cases are indistinguishable in the response on purpose, see Authentication
404 not-found the route doesn’t exist (only POST /v1/compress, GET /v1/health, and OPTIONS /v1/compress are real)
411 length-required no Content-Length header was sent — refused before the body is ever read, since there’s no way to enforce the byte cap on a body of unknown size
413 input-too-large the upload exceeds MAX_INPUT_BYTES — refused from Content-Length alone when possible, before the body is buffered; see Limits
415 unsupported-format the file isn’t a recognisable image, or it’s HEIC/AVIF — this endpoint refuses both explicitly, see Limits
422 decode-failed bytes passed format sniffing but the decoder rejected them (truncated or corrupt file)
422 dimension-exceeded decoded dimensions exceed MAX_MEGAPIXELS, checked from the header before a full decode
422 target-unreachable the search couldn’t reach target — an answer, not a failure, see below
429 rate-limited this key has exceeded 10 requests / 60 s — response carries a Retry-After header with the seconds left in the key’s own window
500 internal an unexpected failure — either a genuine bug, or this Worker’s own configuration is broken (a missing/invalid wrangler.jsonc var); the message names exactly which
503 processing-timeout the request’s abort signal fired — read Limits before assuming this fires reliably on every slow request
503 rate-limiter-unavailable the per-key rate limiter itself could not be reached, so the request was refused rather than served unmetered — the limiter fails closed on purpose, see Authentication; carries Retry-After: 60

target-unreachable is an answer, not a failure

When the byte-target search can’t land target at all, the response is still 422, but the body carries three extra fields beyond error/message:

{
  "error": "target-unreachable",
  "message": "…",
  "reason": "FORMAT_FLOOR",
  "closest": 61440,
  "suggestions": [
    { "kind": "switch-format", "format": "webp" },
    { "kind": "raise-target", "bytes": 65536 }
  ]
}
  • reason is one of FIXED_DIMENSIONS, MIN_DIMENSION, FORMAT_FLOOR, or BUDGET_EXHAUSTED — the same four-way vocabulary the CLI’s exit codes 10–13 and the library’s give-up codes use. See that page for what each one means and what lever fixes it.
  • closest is the byte size of the smallest file the search actually produced — a real, complete result you can choose to accept even though it’s over target.
  • suggestions is the same structured-data vocabulary the CLI’s own stderr sentences and --json output are built from (packages/cli/src/messages.ts), so a caller scripting against both surfaces never has to parse two different descriptions of the same outcome.

Four codes you’ll never see from this API

The engine (@imagekbreducer/image-core) defines nine ErrorCode values in total; this API’s ERROR_TABLE covers all nine, but four of them are folded into the plain 500 internal above rather than getting their own caller-facing code:

  • encode-format-mismatch and no-canvas-surface are canvas-backend-only failures (a browser <canvas> returning the wrong blob.type, or no OffscreenCanvas/document to draw on). This API runs entirely on the wasm backend — the same one the CLI uses — and never constructs a canvas backend, so neither can actually be thrown here. They’re mapped only so the engine’s full ErrorCode union type-checks against this API’s error table; if either ever did fire, that would itself be a bug worth a 500.
  • encode-failed and invalid-op are reachable through this API’s wasm backend, but neither is caused by anything a /v1/compress request supplies — the request body is only file/target/format/allow_downscale, with no crop/rotate/pad parameters for a caller to get wrong. An encode-failed or invalid-op here can only be this API’s own encoder or geometry math failing on otherwise-valid input, which is what 500 internal is for.

If you’re comparing this table against the CLI’s exit codes: the CLI gives invalid-op its own exit code (25), because a CLI user reaches it directly via --crop/--pad flags that this API doesn’t expose. Same engine code, different surface, different honest answer.

Was this page helpful?